diff --git a/packages/@webex/plugin-meetings/src/constants.ts b/packages/@webex/plugin-meetings/src/constants.ts index 5231d3e0184..5d5b493835e 100644 --- a/packages/@webex/plugin-meetings/src/constants.ts +++ b/packages/@webex/plugin-meetings/src/constants.ts @@ -904,6 +904,10 @@ export const DISPLAY_HINTS = { RECORDING_CONTROL_PAUSE: 'RECORDING_CONTROL_PAUSE', RECORDING_CONTROL_STOP: 'RECORDING_CONTROL_STOP', RECORDING_CONTROL_RESUME: 'RECORDING_CONTROL_RESUME', + PREMISE_RECORDING_CONTROL_START: 'PREMISE_RECORDING_CONTROL_START', + PREMISE_RECORDING_CONTROL_PAUSE: 'PREMISE_RECORDING_CONTROL_PAUSE', + PREMISE_RECORDING_CONTROL_STOP: 'PREMISE_RECORDING_CONTROL_STOP', + PREMISE_RECORDING_CONTROL_RESUME: 'PREMISE_RECORDING_CONTROL_RESUME', LOCK_CONTROL_UNLOCK: 'LOCK_CONTROL_UNLOCK', LOCK_CONTROL_LOCK: 'LOCK_CONTROL_LOCK', LOCK_STATUS_LOCKED: 'LOCK_STATUS_LOCKED', diff --git a/packages/@webex/plugin-meetings/src/meeting/in-meeting-actions.ts b/packages/@webex/plugin-meetings/src/meeting/in-meeting-actions.ts index f803251507c..6b6c80894f5 100644 --- a/packages/@webex/plugin-meetings/src/meeting/in-meeting-actions.ts +++ b/packages/@webex/plugin-meetings/src/meeting/in-meeting-actions.ts @@ -26,6 +26,7 @@ interface IInMeetingActions { canStartRecording?: boolean; canPauseRecording?: boolean; canResumeRecording?: boolean; + isPremiseRecordingEnabled?: boolean; canStopRecording?: boolean; canRaiseHand?: boolean; canLowerAllHands?: boolean; @@ -121,6 +122,8 @@ export default class InMeetingActions implements IInMeetingActions { canResumeRecording = null; + isPremiseRecordingEnabled = null; + canStopRecording = null; canSetMuteOnEntry = null; @@ -301,6 +304,7 @@ export default class InMeetingActions implements IInMeetingActions { canPauseRecording: this.canPauseRecording, canResumeRecording: this.canResumeRecording, canStopRecording: this.canStopRecording, + isPremiseRecordingEnabled: this.isPremiseRecordingEnabled, canRaiseHand: this.canRaiseHand, canLowerAllHands: this.canLowerAllHands, canLowerSomeoneElsesHand: this.canLowerSomeoneElsesHand, diff --git a/packages/@webex/plugin-meetings/src/meeting/index.ts b/packages/@webex/plugin-meetings/src/meeting/index.ts index ba985aef88f..f5311e002e3 100644 --- a/packages/@webex/plugin-meetings/src/meeting/index.ts +++ b/packages/@webex/plugin-meetings/src/meeting/index.ts @@ -3774,6 +3774,10 @@ export default class Meeting extends StatelessWebexPlugin { this.userDisplayHints, this.selfUserPolicies ), + isPremiseRecordingEnabled: RecordingUtil.isPremiseRecordingEnabled( + this.userDisplayHints, + this.selfUserPolicies + ), canRaiseHand: MeetingUtil.canUserRaiseHand(this.userDisplayHints), canLowerAllHands: MeetingUtil.canUserLowerAllHands(this.userDisplayHints), canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(this.userDisplayHints), diff --git a/packages/@webex/plugin-meetings/src/recording-controller/enums.ts b/packages/@webex/plugin-meetings/src/recording-controller/enums.ts index a6089b59bde..92ebff556c4 100644 --- a/packages/@webex/plugin-meetings/src/recording-controller/enums.ts +++ b/packages/@webex/plugin-meetings/src/recording-controller/enums.ts @@ -1,8 +1,11 @@ -enum RecordingAction { +export enum RecordingAction { Start = 'Start', Stop = 'Stop', Pause = 'Pause', Resume = 'Resume', } -export default RecordingAction; +export enum RecordingType { + Premise = 'premise', + Cloud = 'cloud', +} diff --git a/packages/@webex/plugin-meetings/src/recording-controller/index.ts b/packages/@webex/plugin-meetings/src/recording-controller/index.ts index 5ffabd358be..e71f56db767 100644 --- a/packages/@webex/plugin-meetings/src/recording-controller/index.ts +++ b/packages/@webex/plugin-meetings/src/recording-controller/index.ts @@ -1,9 +1,9 @@ import PermissionError from '../common/errors/permission'; +import LoggerProxy from '../common/logs/logger-proxy'; import {CONTROLS, HTTP_VERBS, SELF_POLICY} from '../constants'; import MeetingRequest from '../meeting/request'; -import RecordingAction from './enums'; +import {RecordingAction, RecordingType} from './enums'; import Util from './util'; -import LoggerProxy from '../common/logs/logger-proxy'; /** * @description Recording manages the recording functionality of the meeting object, there should only be one instantation of recording per meeting @@ -228,11 +228,12 @@ export default class RecordingController { /** * @param {RecordingAction} action + * @param {RecordingType} recordingType * @private * @memberof RecordingController * @returns {Promise} */ - private recordingService(action: RecordingAction): Promise { + private recordingService(action: RecordingAction, recordingType: RecordingType): Promise { // @ts-ignore return this.request.request({ body: { @@ -242,6 +243,7 @@ export default class RecordingController { recording: { action: action.toLowerCase(), }, + recordingType, }, uri: `${this.serviceUrl}/loci/${this.locusId}/recording`, method: HTTP_VERBS.PUT, @@ -276,14 +278,25 @@ export default class RecordingController { * @returns {Promise} */ private recordingFacade(action: RecordingAction): Promise { + const isPremiseRecordingEnabled = Util.isPremiseRecordingEnabled( + this.displayHints, + this.selfUserPolicies + ); LoggerProxy.logger.log( `RecordingController:index#recordingFacade --> recording action [${action}]` ); + let recordingType: RecordingType; + if (isPremiseRecordingEnabled) { + recordingType = RecordingType.Premise; + } else { + recordingType = RecordingType.Cloud; + } + // assumes action is proper cased (i.e., Example) if (Util?.[`canUser${action}`](this.displayHints, this.selfUserPolicies)) { if (this.serviceUrl) { - return this.recordingService(action); + return this.recordingService(action, recordingType); } return this.recordingControls(action); diff --git a/packages/@webex/plugin-meetings/src/recording-controller/util.ts b/packages/@webex/plugin-meetings/src/recording-controller/util.ts index 807decd1aa6..88a242bde8d 100644 --- a/packages/@webex/plugin-meetings/src/recording-controller/util.ts +++ b/packages/@webex/plugin-meetings/src/recording-controller/util.ts @@ -1,33 +1,47 @@ import {DISPLAY_HINTS, SELF_POLICY} from '../constants'; -import RecordingAction from './enums'; +import {RecordingAction} from './enums'; import MeetingUtil from '../meeting/util'; const canUserStart = ( displayHints: Array, userPolicies: Record ): boolean => - displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) && + (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START)) && MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies); const canUserPause = ( displayHints: Array, userPolicies: Record ): boolean => - displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) && + (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE)) && MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies); const canUserResume = ( displayHints: Array, userPolicies: Record ): boolean => - displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) && + (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) && MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies); const canUserStop = ( displayHints: Array, userPolicies: Record ): boolean => - displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) && + (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP)) && + MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies); + +const isPremiseRecordingEnabled = ( + displayHints: Array, + userPolicies: Record +): boolean => + (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) || + displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) && MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies); const extractLocusId = (url: string) => { @@ -70,6 +84,7 @@ export default { canUserPause, canUserResume, canUserStop, + isPremiseRecordingEnabled, deriveRecordingStates, extractLocusId, }; diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meeting/in-meeting-actions.ts b/packages/@webex/plugin-meetings/test/unit/spec/meeting/in-meeting-actions.ts index d6b2dec374b..bd673f7acb6 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meeting/in-meeting-actions.ts +++ b/packages/@webex/plugin-meetings/test/unit/spec/meeting/in-meeting-actions.ts @@ -33,6 +33,7 @@ describe('plugin-meetings', () => { canStartManualCaption: null, canStopManualCaption: null, isManualCaptionActive: null, + isPremiseRecordingEnabled: null, isSaveTranscriptsEnabled: null, isWebexAssistantActive: null, canViewCaptionPanel: null, @@ -131,6 +132,7 @@ describe('plugin-meetings', () => { 'canStartManualCaption', 'canStopManualCaption', 'isManualCaptionActive', + 'isPremiseRecordingEnabled', 'isSaveTranscriptsEnabled', 'isWebexAssistantActive', 'canViewCaptionPanel', diff --git a/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/index.js b/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/index.js index 5e45c719162..6fe2abda085 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/index.js @@ -221,7 +221,21 @@ describe('plugin-meetings', () => { assert.calledWith(request.request, { uri: `test/loci/id/recording`, - body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'start'}}, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'start'}, recordingType: 'cloud'}, + method: HTTP_VERBS.PUT, + }); + + assert.deepEqual(result, request.request.firstCall.returnValue); + }); + + it('can start premise recording when the correct display hint is present', () => { + controller.setDisplayHints(['PREMISE_RECORDING_CONTROL_START']); + + const result = controller.startRecording(); + + assert.calledWith(request.request, { + uri: `test/loci/id/recording`, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'start'}, recordingType: 'premise'}, method: HTTP_VERBS.PUT, }); @@ -238,14 +252,28 @@ describe('plugin-meetings', () => { assert.isRejected(result); }); - it('can start recording when the correct display hint is present', () => { + it('can stop recording when the correct display hint is present', () => { controller.setDisplayHints(['RECORDING_CONTROL_STOP']); const result = controller.stopRecording(); assert.calledWith(request.request, { uri: `test/loci/id/recording`, - body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'stop'}}, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'stop'}, recordingType: 'cloud'}, + method: HTTP_VERBS.PUT, + }); + + assert.deepEqual(result, request.request.firstCall.returnValue); + }); + + it('can stop premise recording when the correct display hint is present', () => { + controller.setDisplayHints(['PREMISE_RECORDING_CONTROL_STOP']); + + const result = controller.stopRecording(); + + assert.calledWith(request.request, { + uri: `test/loci/id/recording`, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'stop'}, recordingType: 'premise'}, method: HTTP_VERBS.PUT, }); @@ -269,7 +297,21 @@ describe('plugin-meetings', () => { assert.calledWith(request.request, { uri: `test/loci/id/recording`, - body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'pause'}}, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'pause'}, recordingType: 'cloud'}, + method: HTTP_VERBS.PUT, + }); + + assert.deepEqual(result, request.request.firstCall.returnValue); + }); + + it('can pause premise recording when the correct display hint is present', () => { + controller.setDisplayHints(['PREMISE_RECORDING_CONTROL_PAUSE']); + + const result = controller.pauseRecording(); + + assert.calledWith(request.request, { + uri: `test/loci/id/recording`, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'pause'}, recordingType: 'premise'}, method: HTTP_VERBS.PUT, }); @@ -293,7 +335,21 @@ describe('plugin-meetings', () => { assert.calledWith(request.request, { uri: `test/loci/id/recording`, - body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'resume'}}, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'resume'}, recordingType: 'cloud'}, + method: HTTP_VERBS.PUT, + }); + + assert.deepEqual(result, request.request.firstCall.returnValue); + }); + + it('can resume premise recording when the correct display hint is present', () => { + controller.setDisplayHints(['PREMISE_RECORDING_CONTROL_RESUME']); + + const result = controller.resumeRecording(); + + assert.calledWith(request.request, { + uri: `test/loci/id/recording`, + body: {meetingInfo: {locusSessionId: 'testId'}, recording: {action: 'resume'}, recordingType: 'premise'}, method: HTTP_VERBS.PUT, }); diff --git a/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/util.js b/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/util.js index 2dcc54040a8..0358a7bf148 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/util.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/recording-controller/util.js @@ -1,5 +1,5 @@ import RecordingUtil from '@webex/plugin-meetings/src/recording-controller/util'; -import RecordingAction from '@webex/plugin-meetings/src/recording-controller/enums'; +import { RecordingAction } from '@webex/plugin-meetings/src/recording-controller/enums'; import {SELF_POLICY} from '@webex/plugin-meetings/src/constants'; import {assert} from 'chai'; @@ -29,6 +29,15 @@ describe('plugin-meetings', () => { ); }); + it('can start premise recording when the correct display hint is present', () => { + locusInfo.parsedLocus.info.userDisplayHints.push('PREMISE_RECORDING_CONTROL_START'); + + assert.equal( + RecordingUtil.canUserStart(locusInfo.parsedLocus.info.userDisplayHints), + true + ); + }); + it('can start recording when the correct display hint is present and the policy is true', () => { locusInfo.parsedLocus.info.userDisplayHints.push('RECORDING_CONTROL_START'); @@ -69,6 +78,15 @@ describe('plugin-meetings', () => { ); }); + it('can pause premise recording when the correct display hint is present', () => { + locusInfo.parsedLocus.info.userDisplayHints.push('PREMISE_RECORDING_CONTROL_PAUSE'); + + assert.equal( + RecordingUtil.canUserPause(locusInfo.parsedLocus.info.userDisplayHints), + true + ); + }); + it('can pause recording when the correct display hint is present and the policy is true', () => { locusInfo.parsedLocus.info.userDisplayHints.push('RECORDING_CONTROL_PAUSE'); @@ -109,6 +127,15 @@ describe('plugin-meetings', () => { ); }); + it('can stop premise recording when the correct display hint is present', () => { + locusInfo.parsedLocus.info.userDisplayHints.push('PREMISE_RECORDING_CONTROL_STOP'); + + assert.equal( + RecordingUtil.canUserStop(locusInfo.parsedLocus.info.userDisplayHints), + true + ); + }); + it('can stop recording when the correct display hint is present and the policy is true', () => { locusInfo.parsedLocus.info.userDisplayHints.push('RECORDING_CONTROL_STOP', { [SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD]: true, @@ -142,7 +169,7 @@ describe('plugin-meetings', () => { }); describe('canUserResume', () => { - it('can start recording when the correct display hint is present', () => { + it('can resume recording when the correct display hint is present', () => { locusInfo.parsedLocus.info.userDisplayHints.push('RECORDING_CONTROL_RESUME'); assert.equal( @@ -151,7 +178,16 @@ describe('plugin-meetings', () => { ); }); - it('can start recording when the correct display hint is present and the policy is true', () => { + it('can resume premise recording when the correct display hint is present', () => { + locusInfo.parsedLocus.info.userDisplayHints.push('PREMISE_RECORDING_CONTROL_RESUME'); + + assert.equal( + RecordingUtil.canUserResume(locusInfo.parsedLocus.info.userDisplayHints), + true + ); + }); + + it('can resume recording when the correct display hint is present and the policy is true', () => { locusInfo.parsedLocus.info.userDisplayHints.push('RECORDING_CONTROL_RESUME'); assert.equal( diff --git a/packages/@webex/webex-core/src/lib/webex-http-error.js b/packages/@webex/webex-core/src/lib/webex-http-error.js index 766fffe3da8..dbf4db970c5 100644 --- a/packages/@webex/webex-core/src/lib/webex-http-error.js +++ b/packages/@webex/webex-core/src/lib/webex-http-error.js @@ -22,6 +22,15 @@ export default class WebexHttpError extends HttpError { value: res.options, }); + Reflect.defineProperty(this, 'body', { + enumerable: false, + value: res.body, + }); + + if (this.body && this.body.errorCode) { + message += `\nerrorCode : ${this.body.errorCode}`; + } + if (this.options.url) { message += `\n${this.options.method} ${this.options.url}`; } else if (this.options.uri) {