mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 03:12:29 +00:00
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:
committed by
GitHub
parent
f78ebbb9a9
commit
8a2e4bc628
@@ -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.
|
||||
*
|
||||
|
||||
17
modules/API/external/external_api.js
vendored
17
modules/API/external/external_api.js
vendored
@@ -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;
|
||||
|
||||
9
react/features/desktop-picker/actionTypes.ts
Normal file
9
react/features/desktop-picker/actionTypes.ts
Normal 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';
|
||||
@@ -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);
|
||||
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));
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
9
react/features/desktop-picker/constants.ts
Normal file
9
react/features/desktop-picker/constants.ts
Normal file
@@ -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: []
|
||||
|
||||
34
react/features/desktop-picker/reducer.ts
Normal file
34
react/features/desktop-picker/reducer.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
14
react/features/desktop-picker/types.ts
Normal file
14
react/features/desktop-picker/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface IDesktopSources {
|
||||
sources: ISourcesByType;
|
||||
}
|
||||
|
||||
export interface ISourcesByType {
|
||||
screen: [];
|
||||
window: [];
|
||||
}
|
||||
|
||||
export type ElectronWindowType = {
|
||||
JitsiMeetElectron?: {
|
||||
obtainDesktopStreams: Function;
|
||||
} ;
|
||||
} & typeof window;
|
||||
Reference in New Issue
Block a user