feat(screenshare) - add web security fix for electron (#13096)

use send the share screen sources using the external api

---------

Co-authored-by: Gabriel Borlea <gabriel.borlea@8x8.com>
This commit is contained in:
Duduman Bogdan Vlad
2023-10-16 14:59:55 +03:00
committed by GitHub
parent f78ebbb9a9
commit 8a2e4bc628
12 changed files with 290 additions and 41 deletions

View File

@@ -0,0 +1,9 @@
/**
* Action type to set the device sources.
*/
export const SET_DESKTOP_SOURCES = 'SET_DESKTOP_SOURCES';
/**
* Action type to DELETE_DESKTOP_SOURCES.
*/
export const DELETE_DESKTOP_SOURCES = 'DELETE_DESKTOP_SOURCES';

View File

@@ -1,6 +1,8 @@
import { openDialog } from '../base/dialog/actions';
import { DELETE_DESKTOP_SOURCES, SET_DESKTOP_SOURCES } from './actionTypes';
import DesktopPicker from './components/DesktopPicker';
import { _separateSourcesByType } from './functions';
/**
* Signals to open a dialog with the DesktopPicker component.
@@ -18,3 +20,27 @@ export function showDesktopPicker(options: { desktopSharingSources?: any; } = {}
onSourceChoose
});
}
/**
* Signals to open a dialog with the DesktopPicker component with screen sharing sources.
*
* @param {Array} sources - Desktop capturer sources.
* @returns {Function}
*/
export function setDesktopSources(sources: Array<any>) {
return {
type: SET_DESKTOP_SOURCES,
sources: _separateSourcesByType(sources ?? [])
};
}
/**
* Action used to delete desktop sources.
*
* @returns {Object}
*/
export function deleteDesktopSources() {
return {
type: DELETE_DESKTOP_SOURCES
};
}

View File

@@ -1,26 +1,24 @@
import _ from 'lodash';
import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IStore } from '../../app/types';
import { IReduxState, IStore } from '../../app/types';
import { hideDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n/functions';
import Dialog from '../../base/ui/components/web/Dialog';
import Tabs from '../../base/ui/components/web/Tabs';
import { obtainDesktopSources } from '../functions';
import { deleteDesktopSources } from '../actions';
import { THUMBNAIL_SIZE } from '../constants';
import {
getDesktopPickerSources,
obtainDesktopSources,
oldJitsiMeetElectronUsage
} from '../functions';
import { IDesktopSources } from '../types';
import DesktopPickerPane from './DesktopPickerPane';
/**
* The size of the requested thumbnails.
*
* @type {Object}
*/
const THUMBNAIL_SIZE = {
height: 300,
width: 300
};
/**
* The sources polling interval in ms.
*
@@ -47,6 +45,11 @@ const VALID_TYPES = Object.keys(TAB_LABELS);
*/
interface IProps extends WithTranslation {
/**
* An object containing all the DesktopCapturerSources.
*/
_sources: IDesktopSources;
/**
* An array with desktop sharing sources to be displayed.
*/
@@ -182,6 +185,29 @@ class DesktopPicker extends PureComponent<IProps, IState> {
this._stopPolling();
}
/**
* Clean up component and DesktopCapturerSource store state.
*
* @inheritdoc
*/
componentDidUpdate(prevProps: IProps) {
// skip logic if old jitsi meet electron used.
if (oldJitsiMeetElectronUsage()) {
return;
}
if (this.props._sources && !_.isEqual(this.props._sources, prevProps._sources)) {
const selectedSource = this._getSelectedSource(this.props._sources);
// update state with latest thumbnail desktop sources
this.setState({
sources: this.props._sources,
selectedSource
});
}
}
/**
* Implements React's {@link Component#render()}.
*
@@ -282,6 +308,7 @@ class DesktopPicker extends PureComponent<IProps, IState> {
_onCloseModal(id = '', type?: string, screenShareAudio = false) {
this.props.onSourceChoose(id, type, screenShareAudio);
this.props.dispatch(hideDialog());
this.props.dispatch(deleteDesktopSources());
}
/**
@@ -322,14 +349,14 @@ class DesktopPicker extends PureComponent<IProps, IState> {
_onTabSelected(id: string) {
const { sources } = this.state;
this._selectedTabType = id;
// When we change tabs also reset the screenShareAudio state so we don't
// use the option from one tab when sharing from another.
this.setState({
screenShareAudio: false,
selectedSource: this._getSelectedSource(sources),
selectedTab: id
// select type `window` or `screen` from id
selectedTab: id.split('-')[0]
});
}
@@ -406,24 +433,44 @@ class DesktopPicker extends PureComponent<IProps, IState> {
_updateSources() {
const { types } = this.state;
if (types.length > 0) {
obtainDesktopSources(
this.state.types,
{ thumbnailSize: THUMBNAIL_SIZE }
)
.then((sources: any) => {
const selectedSource = this._getSelectedSource(sources);
if (oldJitsiMeetElectronUsage()) {
// TODO: Maybe check if we have stopped the timer and unmounted
// the component.
this.setState({
sources,
selectedSource
});
})
.catch(() => { /* ignore */ });
if (types.length > 0) {
obtainDesktopSources(
this.state.types,
{ thumbnailSize: THUMBNAIL_SIZE }
)
.then((sources: any) => {
const selectedSource = this._getSelectedSource(sources);
this.setState({
sources,
selectedSource
});
})
.catch(() => { /* ignore */ });
}
} else {
APP.API.notifyRequestDesktopSources({
types,
thumbnailSize: THUMBNAIL_SIZE
});
}
}
}
export default translate(connect()(DesktopPicker));
/**
* Maps (parts of) the redux state to the React props.
*
* @param {Object} state - The redux state.
* @returns {{
* _sources: IDesktopPicker
* }}
*/
function _mapStateToProps(state: IReduxState) {
return {
_sources: getDesktopPickerSources(state)
};
}
export default translate(connect(_mapStateToProps)(DesktopPicker));

View File

@@ -74,12 +74,7 @@ class DesktopSourcePreview extends Component<IProps> {
className = { displayClasses }
onClick = { this._onClick }
onDoubleClick = { this._onDoubleClick }>
<div className = 'desktop-source-preview-image-container'>
<img
alt = { this.props.t('welcomepage.logo.desktopPreviewThumbnail') }
className = 'desktop-source-preview-thumbnail'
src = { this.props.source.thumbnail.toDataURL() } />
</div>
{this._renderThumbnailImageContainer()}
<div className = 'desktop-source-preview-label'>
{ this.props.source.name }
</div>
@@ -87,6 +82,43 @@ class DesktopSourcePreview extends Component<IProps> {
);
}
/**
* Render thumbnail screenshare image.
*
* @returns {Object} - Thumbnail image.
*/
_renderThumbnailImageContainer() {
// default data URL for thumnbail image
let srcImage = this.props.source.thumbnail.dataUrl;
// legacy thumbnail image
if (typeof this.props.source.thumbnail.toDataURL === 'function') {
srcImage = this.props.source.thumbnail.toDataURL();
}
return (
<div className = 'desktop-source-preview-image-container'>
{ this._renderThumbnailImage(srcImage) }
</div>
);
}
/**
* Render thumbnail screenshare image.
*
* @param {string} src - Of the image.
* @returns {Object} - Thumbnail image.
*/
_renderThumbnailImage(src: string) {
return (
<img
alt = { this.props.t('welcomepage.logo.desktopPreviewThumbnail') }
className = 'desktop-source-preview-thumbnail'
src = { src } />
);
}
/**
* Invokes the passed in onClick callback.
*

View File

@@ -0,0 +1,9 @@
/**
* The size of the requested thumbnails.
*
* @type {Object}
*/
export const THUMBNAIL_SIZE = {
height: 300,
width: 300
};

View File

@@ -1,4 +1,28 @@
import { IReduxState } from '../app/types';
import logger from './logger';
import { ElectronWindowType } from './types';
/**
* Returns root conference state.
*
* @param {IReduxState} state - Global state.
* @returns {Object} Conference state.
*/
export const getDesktopPicker = (state: IReduxState) => state['features/desktop-picker'];
/**
* Selector to return a list of knocking participants.
*
* @param {IReduxState} state - State object.
* @returns {IDesktopSources}
*/
export function getDesktopPickerSources(state: IReduxState) {
const root = getDesktopPicker(state);
return root.sources;
}
/**
* Begins a request to get available DesktopCapturerSources.
@@ -20,7 +44,7 @@ export function obtainDesktopSources(types: string[], options: { thumbnailSize?:
}
return new Promise((resolve, reject) => {
const { JitsiMeetElectron } = window;
const { JitsiMeetElectron } = window as ElectronWindowType;
if (JitsiMeetElectron?.obtainDesktopStreams) {
JitsiMeetElectron.obtainDesktopStreams(
@@ -43,6 +67,20 @@ export function obtainDesktopSources(types: string[], options: { thumbnailSize?:
});
}
/**
* Check usage of old jitsi meet electron version.
*
* @returns {boolean} True if we use old jitsi meet electron, otherwise false.
*/
export function oldJitsiMeetElectronUsage() {
const { JitsiMeetElectron } = window as ElectronWindowType;
if (JitsiMeetElectron?.obtainDesktopStreams) {
return true;
}
return false;
}
/**
* Converts an array of DesktopCapturerSources to an object with types for keys
@@ -53,7 +91,7 @@ export function obtainDesktopSources(types: string[], options: { thumbnailSize?:
* @returns {Object} An object with the sources split into separate arrays based
* on source type.
*/
function _separateSourcesByType(sources: Array<{ id: string; }> = []) {
export function _separateSourcesByType(sources: Array<{ id: string; }> = []) {
const sourcesByType: any = {
screen: [],
window: []

View File

@@ -0,0 +1,34 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
import { DELETE_DESKTOP_SOURCES, SET_DESKTOP_SOURCES } from './actionTypes';
import { IDesktopSources } from './types';
/**
* The initial state of the web-hid feature.
*/
const DEFAULT_STATE: IDesktopPicker = {
sources: {} as IDesktopSources
};
export interface IDesktopPicker {
sources: IDesktopSources;
}
ReducerRegistry.register<IDesktopPicker>(
'features/desktop-picker',
(state: IDesktopPicker = DEFAULT_STATE, action): IDesktopPicker => {
switch (action.type) {
case SET_DESKTOP_SOURCES:
return {
...state,
sources: action.sources
};
case DELETE_DESKTOP_SOURCES:
return {
...state,
...DEFAULT_STATE
};
default:
return state;
}
});

View File

@@ -0,0 +1,14 @@
export interface IDesktopSources {
sources: ISourcesByType;
}
export interface ISourcesByType {
screen: [];
window: [];
}
export type ElectronWindowType = {
JitsiMeetElectron?: {
obtainDesktopStreams: Function;
} ;
} & typeof window;