import { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { createRecordingDialogEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { IJitsiConference } from '../../../base/conference/reducer';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { updateDropboxToken } from '../../../dropbox/actions';
import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
import { showErrorNotification } from '../../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../constants';
import { isRecordingSharingEnabled, shouldAutoTranscribeOnRecord, supportsLocalRecording } from '../../functions';
export interface IProps extends WithTranslation {
/**
* The app key for the dropbox authentication.
*/
_appKey: string;
/**
* Requests transcribing when recording is turned on.
*/
_autoTranscribeOnRecord: boolean;
/**
* The {@code JitsiConference} for the current conference.
*/
_conference?: IJitsiConference;
/**
* Whether subtitles should be displayed or not.
*/
_displaySubtitles?: boolean;
/**
* Whether to show file recordings service, even if integrations
* are enabled.
*/
_fileRecordingsServiceEnabled: boolean;
/**
* Whether to show the possibility to share file recording with other people (e.g. Meeting participants), based on
* the actual implementation on the backend.
*/
_fileRecordingsServiceSharingEnabled: boolean;
/**
* If true the dropbox integration is enabled, otherwise - disabled.
*/
_isDropboxEnabled: boolean;
/**
* Whether or not local recording is enabled.
*/
_localRecordingEnabled: boolean;
/**
* The dropbox refresh token.
*/
_rToken: string;
/**
* Whether the record audio / video option is enabled by default.
*/
_recordAudioAndVideo: boolean;
/**
* Whether or not the local participant is screensharing.
*/
_screensharing: boolean;
/**
* Whether or not the screenshot capture feature is enabled.
*/
_screenshotCaptureEnabled: boolean;
/**
* The selected language for subtitles.
*/
_subtitlesLanguage: string | null;
/**
* The dropbox access token.
*/
_token: string;
/**
* Access token's expiration date as UNIX timestamp.
*/
_tokenExpireDate?: number;
/**
* The redux dispatch function.
*/
dispatch: IStore['dispatch'];
navigation: any;
}
interface IState {
/**
* true if we have valid oauth token.
*/
isTokenValid: boolean;
/**
* true if we are in process of validating the oauth token.
*/
isValidating: boolean;
/**
* Whether the local recording should record just the local user streams.
*/
localRecordingOnlySelf: boolean;
/**
* The currently selected recording service of type: RECORDING_TYPES.
*/
selectedRecordingService: string;
/**
* True if the user requested the service to share the recording with others.
*/
sharingEnabled: boolean;
/**
* True if the user requested the service to record audio and video.
*/
shouldRecordAudioAndVideo: boolean;
/**
* True if the user requested the service to record transcription.
*/
shouldRecordTranscription: boolean;
/**
* Number of MiB of available space in user's Dropbox account.
*/
spaceLeft?: number;
/**
* The display name of the user's Dropbox account.
*/
userName?: string;
}
/**
* Component for the recording start dialog.
*/
class AbstractStartRecordingDialog extends Component {
/**
* Initializes a new {@code StartRecordingDialog} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onSubmit = this._onSubmit.bind(this);
this._onSelectedRecordingServiceChanged
= this._onSelectedRecordingServiceChanged.bind(this);
this._onSharingSettingChanged = this._onSharingSettingChanged.bind(this);
this._toggleScreenshotCapture = this._toggleScreenshotCapture.bind(this);
this._onLocalRecordingSelfChange = this._onLocalRecordingSelfChange.bind(this);
this._onTranscriptionChange = this._onTranscriptionChange.bind(this);
this._onRecordAudioAndVideoChange = this._onRecordAudioAndVideoChange.bind(this);
let selectedRecordingService = '';
// TODO: Potentially check if we need to handle changes of
// _fileRecordingsServiceEnabled and _areIntegrationsEnabled()
if (this.props._fileRecordingsServiceEnabled
|| !this._areIntegrationsEnabled()) {
selectedRecordingService = RECORDING_TYPES.JITSI_REC_SERVICE;
} else if (this._areIntegrationsEnabled()) {
if (props._localRecordingEnabled && supportsLocalRecording()) {
selectedRecordingService = RECORDING_TYPES.LOCAL;
} else {
selectedRecordingService = RECORDING_TYPES.DROPBOX;
}
}
this.state = {
isTokenValid: false,
isValidating: false,
userName: undefined,
sharingEnabled: true,
shouldRecordAudioAndVideo: this.props._recordAudioAndVideo,
shouldRecordTranscription: this.props._autoTranscribeOnRecord,
spaceLeft: undefined,
selectedRecordingService,
localRecordingOnlySelf: false
};
}
/**
* Validates the oauth access token.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
if (typeof this.props._token !== 'undefined') {
this._onTokenUpdated();
}
}
/**
* Validates the oauth access token.
*
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps: IProps) {
if (this.props._token !== prevProps._token) {
this._onTokenUpdated();
}
}
/**
* Returns true if the integrations with third party services are enabled
* and false otherwise.
*
* @returns {boolean} - True if the integrations with third party services
* are enabled and false otherwise.
*/
_areIntegrationsEnabled() {
return this.props._isDropboxEnabled;
}
/**
* Callback to handle sharing setting change from the dialog.
*
* @returns {void}
*/
_onSharingSettingChanged() {
this.setState({
sharingEnabled: !this.state.sharingEnabled
});
}
/**
* Callback to handle local recording only self setting change.
*
* @returns {void}
*/
_onLocalRecordingSelfChange() {
this.setState({
localRecordingOnlySelf: !this.state.localRecordingOnlySelf
});
}
/**
* Handles selected recording service changes.
*
* @param {string} selectedRecordingService - The new selected recording
* service.
* @returns {void}
*/
_onSelectedRecordingServiceChanged(selectedRecordingService: string) {
this.setState({ selectedRecordingService }, () => {
this.props.dispatch(setSelectedRecordingService(selectedRecordingService));
});
}
/**
* Handles transcription switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onTranscriptionChange(value: boolean) {
this.setState({
shouldRecordTranscription: value
});
}
/**
* Handles audio and video switch change.
*
* @param {boolean} value - The new value.
* @returns {void}
*/
_onRecordAudioAndVideoChange(value: boolean) {
this.setState({
shouldRecordAudioAndVideo: value
});
}
/**
* Validates the dropbox access token and fetches account information.
*
* @returns {void}
*/
_onTokenUpdated() {
const { _appKey, _isDropboxEnabled, _token, _rToken, _tokenExpireDate, dispatch } = this.props;
if (!_isDropboxEnabled) {
return;
}
if (typeof _token === 'undefined') {
this.setState({
isTokenValid: false,
isValidating: false
});
} else { // @ts-ignore
if (_tokenExpireDate && Date.now() > new Date(_tokenExpireDate)) {
getNewAccessToken(_appKey, _rToken)
.then((resp: { expireDate: number; rToken: string; token: string; }) =>
dispatch(updateDropboxToken(resp.token, resp.rToken, resp.expireDate)));
return;
}
this.setState({
isTokenValid: false,
isValidating: true
});
getDropboxData(_token, _appKey).then(data => {
if (typeof data === 'undefined') {
this.setState({
isTokenValid: false,
isValidating: false
});
} else {
this.setState({
isTokenValid: true,
isValidating: false,
...data
});
}
});
}
}
/**
* Starts a file recording session.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
const {
_appKey,
_conference,
_displaySubtitles,
_isDropboxEnabled,
_rToken,
_subtitlesLanguage,
_token,
dispatch
} = this.props;
let appData;
const attributes: {
type?: string;
} = {};
if (this.state.shouldRecordAudioAndVideo) {
switch (this.state.selectedRecordingService) {
case RECORDING_TYPES.DROPBOX: {
if (_isDropboxEnabled && _token) {
appData = JSON.stringify({
'file_recording_metadata': {
'upload_credentials': {
'service_name': RECORDING_TYPES.DROPBOX,
'token': _token,
'r_token': _rToken,
'app_key': _appKey
}
}
});
attributes.type = RECORDING_TYPES.DROPBOX;
} else {
dispatch(showErrorNotification({
titleKey: 'dialog.noDropboxToken'
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
return;
}
break;
}
case RECORDING_TYPES.JITSI_REC_SERVICE: {
appData = JSON.stringify({
'file_recording_metadata': {
'share': this.state.sharingEnabled
}
});
attributes.type = RECORDING_TYPES.JITSI_REC_SERVICE;
break;
}
case RECORDING_TYPES.LOCAL: {
dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
return true;
}
}
sendAnalytics(
createRecordingDialogEvent('start', 'confirm.button', attributes)
);
this._toggleScreenshotCapture();
_conference?.startRecording({
mode: JitsiRecordingConstants.mode.FILE,
appData
});
}
if (this.state.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE
&& this.state.shouldRecordTranscription) {
dispatch(setRequestingSubtitles(true, _displaySubtitles, _subtitlesLanguage));
}
_conference?.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
isTranscribingEnabled: this.state.shouldRecordTranscription
});
return true;
}
/**
* Toggles screenshot capture feature.
*
* @returns {void}
*/
_toggleScreenshotCapture() {
// To be implemented by subclass.
}
/**
* Renders the platform specific dialog content.
*
* @protected
* @returns {React$Component}
*/
_renderDialogContent: () => React.Component;
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code StartRecordingDialog} component.
*
* @param {Object} state - The Redux state.
* @param {any} _ownProps - Component's own props.
* @private
* @returns {IProps}
*/
export function mapStateToProps(state: IReduxState, _ownProps: any) {
const {
recordingService,
dropbox = { appKey: undefined },
localRecording,
recordings = { recordAudioAndVideo: true }
} = state['features/base/config'];
const {
_displaySubtitles,
_language: _subtitlesLanguage
} = state['features/subtitles'];
return {
_appKey: dropbox.appKey ?? '',
_autoTranscribeOnRecord: shouldAutoTranscribeOnRecord(state),
_conference: state['features/base/conference'].conference,
_displaySubtitles,
_fileRecordingsServiceEnabled: recordingService?.enabled ?? false,
_fileRecordingsServiceSharingEnabled: isRecordingSharingEnabled(state),
_isDropboxEnabled: isDropboxEnabled(state),
_localRecordingEnabled: !localRecording?.disable,
_rToken: state['features/dropbox'].rToken ?? '',
_recordAudioAndVideo: recordings?.recordAudioAndVideo ?? true,
_subtitlesLanguage,
_tokenExpireDate: state['features/dropbox'].expireDate,
_token: state['features/dropbox'].token ?? ''
};
}
export default AbstractStartRecordingDialog;