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>
pull/13958/head jitsi-meet_9031
Duduman Bogdan Vlad 2 years ago committed by GitHub
parent f78ebbb9a9
commit 8a2e4bc628
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      modules/API/API.js
  2. 17
      modules/API/external/external_api.js
  3. 1
      react/features/app/reducers.web.ts
  4. 2
      react/features/app/types.ts
  5. 9
      react/features/desktop-picker/actionTypes.ts
  6. 26
      react/features/desktop-picker/actions.ts
  7. 111
      react/features/desktop-picker/components/DesktopPicker.tsx
  8. 44
      react/features/desktop-picker/components/DesktopSourcePreview.tsx
  9. 9
      react/features/desktop-picker/constants.ts
  10. 42
      react/features/desktop-picker/functions.ts
  11. 34
      react/features/desktop-picker/reducer.ts
  12. 14
      react/features/desktop-picker/types.ts

@ -67,6 +67,7 @@ import {
toggleChat
} from '../../react/features/chat/actions';
import { openChat } from '../../react/features/chat/actions.web';
import { setDesktopSources } from '../../react/features/desktop-picker/actions';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
@ -838,6 +839,16 @@ function initCommands() {
},
'toggle-whiteboard': () => {
APP.store.dispatch(toggleWhiteboard());
},
'_request-desktop-sources-result': data => {
if (data.error) {
logger.error(`Error to retrieve desktop sources result, error data: ${data.error}`);
return;
}
if (data.success?.data?.sources) {
APP.store.dispatch(setDesktopSources(data.success.data.sources));
}
}
};
transport.on('event', ({ data, name }) => {
@ -1279,6 +1290,19 @@ class API {
});
}
/**
* Notify request desktop sources.
*
* @param {Object} options - Object with the options for desktop sources.
* @returns {void}
*/
notifyRequestDesktopSources(options) {
this._sendEvent({
name: '_request-desktop-sources',
options
});
}
/**
* Notify external application that the video quality setting has changed.
*

@ -91,7 +91,8 @@ const commands = {
toggleTileView: 'toggle-tile-view',
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
toggleVideo: 'toggle-video',
toggleWhiteboard: 'toggle-whiteboard'
toggleWhiteboard: 'toggle-whiteboard',
_requestDesktopSourcesResult: '_request-desktop-sources-result'
};
/**
@ -159,7 +160,8 @@ const events = {
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged',
'toolbar-button-clicked': 'toolbarButtonClicked',
'whiteboard-status-changed': 'whiteboardStatusChanged'
'whiteboard-status-changed': 'whiteboardStatusChanged',
'_request-desktop-sources': '_requestDesktopSources'
};
/**
@ -1311,6 +1313,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
}
/**
* Send request to request desktop sources.
*
* @returns {Promise} - Result.
*/
_requestDesktopSources() {
return this._transport.sendRequest({
name: '_request-desktop-sources'
});
}
/**
* Passes an event along to the local conference participant to establish
* or update a direct peer connection. This is currently used for developing

@ -1,6 +1,7 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../base/tooltip/reducer';
import '../desktop-picker/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';
import '../feedback/reducer';

@ -32,6 +32,7 @@ import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
import { ICalendarSyncState } from '../calendar-sync/reducer';
import { IChatState } from '../chat/reducer';
import { IDeepLinkingState } from '../deep-linking/reducer';
import { IDesktopPicker } from '../desktop-picker/reducer';
import { IDropboxState } from '../dropbox/reducer';
import { IDynamicBrandingState } from '../dynamic-branding/reducer';
import { IE2EEState } from '../e2ee/reducer';
@ -123,6 +124,7 @@ export interface IReduxState {
'features/call-integration': ICallIntegrationState;
'features/chat': IChatState;
'features/deep-linking': IDeepLinkingState;
'features/desktop-picker': IDesktopPicker;
'features/dropbox': IDropboxState;
'features/dynamic-branding': IDynamicBrandingState;
'features/e2ee': IE2EEState;

@ -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';

@ -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
};
}

@ -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);
// TODO: Maybe check if we have stopped the timer and unmounted
// the component.
this.setState({
sources,
selectedSource
});
})
.catch(() => { /* ignore */ });
if (oldJitsiMeetElectronUsage()) {
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));

@ -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.
*

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

@ -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: []

@ -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;
}
});

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