mirror of
https://github.com/edumeet/edumeet.git
synced 2026-01-23 02:34:58 +00:00
appIntMeet code
This commit is contained in:
parent
e26c8a5158
commit
550a76bfb0
13 changed files with 444 additions and 72 deletions
|
|
@ -43,7 +43,7 @@
|
|||
"is-electron": "^2.2.0",
|
||||
"marked": "^0.8.0",
|
||||
"material-ui-popup-state": "^1.8.0",
|
||||
"mediasoup-client": "^3.6.55",
|
||||
"mediasoup-client": "^3.7.1",
|
||||
"notistack": "^0.9.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"random-string": "^0.2.0",
|
||||
|
|
@ -51,6 +51,7 @@
|
|||
"react-cookie-consent": "^2.5.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-flip-toolkit": "^7.0.9",
|
||||
"react-iframe": "1.8.5",
|
||||
"react-image-file-resizer": "^0.3.8",
|
||||
"react-images-upload": "^1.2.0",
|
||||
"react-intl": "^3.4.0",
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"riek": "^1.1.0",
|
||||
"socket.io-client": "^2.4.0",
|
||||
"socket.io-client": "^4.5.4",
|
||||
"source-map-explorer": "^2.1.0",
|
||||
"streamsaver": "^2.0.5",
|
||||
"typescript": "^4.2.4",
|
||||
|
|
|
|||
|
|
@ -2110,6 +2110,24 @@ export default class RoomClient
|
|||
peerActions.setStopPeerScreenSharingInProgress(peerId, false));
|
||||
}
|
||||
|
||||
async toggleIframe(iframeUrl)
|
||||
{
|
||||
store.dispatch(
|
||||
roomActions.setToggleIframeInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('toggleIframe', { iframeUrl });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('toggleIframe() [error:"%o"]', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setToggleIframeInProgress(false));
|
||||
}
|
||||
|
||||
async muteAllPeers()
|
||||
{
|
||||
logger.debug('muteAllPeers()');
|
||||
|
|
@ -3194,6 +3212,24 @@ export default class RoomClient
|
|||
break;
|
||||
}
|
||||
|
||||
case 'showIframe':
|
||||
{
|
||||
const { iframeUrl } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
roomActions.openIframe(iframeUrl));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'closeIframe':
|
||||
{
|
||||
store.dispatch(
|
||||
roomActions.closeIframe());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:clearChat':
|
||||
{
|
||||
store.dispatch(chatActions.clearChat());
|
||||
|
|
@ -3917,6 +3953,7 @@ export default class RoomClient
|
|||
chatHistory,
|
||||
fileHistory,
|
||||
lastNHistory,
|
||||
iframeHistory,
|
||||
locked,
|
||||
lobbyPeers,
|
||||
accessCode
|
||||
|
|
@ -3984,6 +4021,9 @@ export default class RoomClient
|
|||
(fileHistory.length > 0) && store.dispatch(
|
||||
fileActions.addFileHistory(fileHistory));
|
||||
|
||||
(iframeHistory) && store.dispatch(
|
||||
roomActions.openIframe(iframeHistory));
|
||||
|
||||
locked ?
|
||||
store.dispatch(roomActions.setRoomLocked()) :
|
||||
store.dispatch(roomActions.setRoomUnLocked());
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { connect } from 'react-redux';
|
|||
import {
|
||||
meProducersSelector,
|
||||
makePermissionSelector,
|
||||
recordingConsentsPeersSelector
|
||||
recordingConsentsPeersSelector,
|
||||
showIframeSelect
|
||||
} from '../../store/selectors';
|
||||
import { permissions } from '../../permissions';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
|
|
@ -60,6 +61,10 @@ const styles = (theme) =>
|
|||
'&.screen' :
|
||||
{
|
||||
order : 2
|
||||
},
|
||||
'&.iframe' :
|
||||
{
|
||||
order : 3
|
||||
}
|
||||
},
|
||||
viewContainer :
|
||||
|
|
@ -156,6 +161,7 @@ const Me = (props) =>
|
|||
roomClient,
|
||||
me,
|
||||
settings,
|
||||
iframeUrl,
|
||||
activeSpeaker,
|
||||
spacing,
|
||||
style,
|
||||
|
|
@ -175,7 +181,7 @@ const Me = (props) =>
|
|||
localRecordingState
|
||||
} = props;
|
||||
|
||||
// const width = style.width;
|
||||
const width = style.width;
|
||||
|
||||
const height = style.height;
|
||||
|
||||
|
|
@ -1018,6 +1024,19 @@ const Me = (props) =>
|
|||
</div>
|
||||
</div>
|
||||
}
|
||||
{iframeUrl &&
|
||||
<div className={classnames(classes.root, 'iframe')} style={spacingStyle}>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<VideoView
|
||||
isMe
|
||||
isIframe
|
||||
iframeUrl={`${iframeUrl }`}
|
||||
videoVisible
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
|
@ -1028,6 +1047,7 @@ Me.propTypes =
|
|||
advancedMode : PropTypes.bool,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object,
|
||||
iframeUrl : PropTypes.string.isRequired,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
micProducer : appPropTypes.Producer,
|
||||
webcamProducer : appPropTypes.Producer,
|
||||
|
|
@ -1073,6 +1093,7 @@ const makeMapStateToProps = () =>
|
|||
me : state.me,
|
||||
...meProducersSelector(state),
|
||||
settings : state.settings,
|
||||
iframeUrl : showIframeSelect(state),
|
||||
activeSpeaker : state.me.id === state.room.activeSpeakerId,
|
||||
hasAudioPermission : canShareAudio(state),
|
||||
hasVideoPermission : canShareVideo(state),
|
||||
|
|
|
|||
|
|
@ -303,24 +303,27 @@ const JoinDialog = ({
|
|||
};
|
||||
|
||||
// TODO: prefix with the Edumeet server HTTP endpoint
|
||||
fetch('/auth/check_login_status', {
|
||||
credentials : 'include',
|
||||
method : 'GET',
|
||||
cache : 'no-cache',
|
||||
redirect : 'follow',
|
||||
referrerPolicy : 'no-referrer' })
|
||||
.then((response) => response.json())
|
||||
.then((json) =>
|
||||
{
|
||||
if (json.loggedIn)
|
||||
if (config.loginEnabled)
|
||||
{
|
||||
fetch('/auth/check_login_status', {
|
||||
credentials : 'include',
|
||||
method : 'GET',
|
||||
cache : 'no-cache',
|
||||
redirect : 'follow',
|
||||
referrerPolicy : 'no-referrer' })
|
||||
.then((response) => response.json())
|
||||
.then((json) =>
|
||||
{
|
||||
roomClient.setLoggedIn(json.loggedIn);
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('Error checking login status', error);
|
||||
});
|
||||
if (json.loggedIn)
|
||||
{
|
||||
roomClient.setLoggedIn(json.loggedIn);
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('Error checking login status', error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,41 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as appPropTypes from '../../appPropTypes';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import PanIcon from '@material-ui/icons/PanTool';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { showIframeSelect, makePermissionSelector } from '../../../store/selectors';
|
||||
import { permissions } from '../../../permissions';
|
||||
import { config } from '../../../config';
|
||||
|
||||
const urlPattern = new RegExp(
|
||||
'^(https?:\\/\\/)?' +
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' +
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?' +
|
||||
'(\\#[-a-z\\d_]*)?$',
|
||||
'i'
|
||||
);
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
display : 'flex',
|
||||
flexDirection : 'column',
|
||||
width : '100%',
|
||||
overflowY : 'auto'
|
||||
},
|
||||
me :
|
||||
{
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
|
|
@ -48,70 +70,163 @@ const styles = (theme) =>
|
|||
|
||||
const ListMe = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
me,
|
||||
iframeUrl,
|
||||
toggleIframeInProgress,
|
||||
hasScreenPermission,
|
||||
settings,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const [ currentUrl, setCurrentUrl ] = useState('');
|
||||
|
||||
const validateUrl = () =>
|
||||
{
|
||||
if (currentUrl === '')
|
||||
return false;
|
||||
|
||||
if (!urlPattern.test(currentUrl))
|
||||
return false;
|
||||
|
||||
if (!currentUrl.toLowerCase().startsWith('https://'))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const isValidUrl = validateUrl();
|
||||
|
||||
const picture = me.picture || EmptyAvatar;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<img alt='My avatar' className={classes.avatar} src={picture} />
|
||||
|
||||
<div className={classes.peerInfo}>
|
||||
{settings.displayName}
|
||||
</div>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
<div className={classes.me}>
|
||||
<img alt='My avatar' className={classes.avatar} src={picture} />
|
||||
<div className={classes.peerInfo}>
|
||||
{settings.displayName}
|
||||
</div>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
className={
|
||||
classnames(me.raisedHand ? classes.green : null, classes.buttons)
|
||||
}
|
||||
disabled={me.raisedHandInProgress}
|
||||
color='primary'
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
placement='bottom'
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.raisedHand',
|
||||
defaultMessage : 'Raise hand'
|
||||
})}
|
||||
className={
|
||||
classnames(me.raisedHand ? classes.green : null, classes.buttons)
|
||||
}
|
||||
disabled={me.raisedHandInProgress}
|
||||
color='primary'
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
roomClient.setRaisedHand(!me.raisedHand);
|
||||
}}
|
||||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
roomClient.setRaisedHand(!me.raisedHand);
|
||||
{hasScreenPermission &&
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
id='displayname'
|
||||
label={intl.formatMessage({
|
||||
id : 'label.iframeAppUrl',
|
||||
defaultMessage : 'External app URL to open for all (https only)'
|
||||
})}
|
||||
value={iframeUrl ?? currentUrl}
|
||||
variant='outlined'
|
||||
margin='normal'
|
||||
disabled={iframeUrl}
|
||||
onChange={(event) => setCurrentUrl(event.target.value.trim())}
|
||||
fullWidth
|
||||
/>
|
||||
{iframeUrl &&
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'room.hideIframe',
|
||||
defaultMessage : 'Hide external app'
|
||||
})}
|
||||
className={classes.button}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
disabled={toggleIframeInProgress}
|
||||
onClick={() => roomClient.toggleIframe(null)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.hideIframe'
|
||||
defaultMessage='Hide external app'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
{!iframeUrl &&
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'room.showIframe',
|
||||
defaultMessage : 'Show external app'
|
||||
})}
|
||||
className={classes.button}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
disabled={toggleIframeInProgress || !isValidUrl}
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.toggleIframe(currentUrl);
|
||||
setCurrentUrl('');
|
||||
}}
|
||||
>
|
||||
<PanIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<FormattedMessage
|
||||
id='room.showIframe'
|
||||
defaultMessage='Show external app'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ListMe.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
iframeUrl : PropTypes.string,
|
||||
toggleIframeInProgress : PropTypes.bool,
|
||||
hasScreenPermission : PropTypes.bool.isRequired,
|
||||
settings : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
me : state.me,
|
||||
settings : state.settings
|
||||
});
|
||||
const makeMapStateToProps = () =>
|
||||
{
|
||||
const canShareScreen =
|
||||
makePermissionSelector(permissions.SHARE_SCREEN);
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
me : state.me,
|
||||
iframeUrl : showIframeSelect(state),
|
||||
toggleIframeInProgress : state.room.toggleIframeInProgress,
|
||||
hasScreenPermission : canShareScreen(state),
|
||||
settings : state.settings
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
makeMapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
|
|
@ -119,6 +234,8 @@ export default withRoomContext(connect(
|
|||
{
|
||||
return (
|
||||
prev.me === next.me &&
|
||||
prev.room.iframeUrl === next.room.iframeUrl &&
|
||||
prev.room.toggleIframeInProgress === next.room.toggleIframeInProgress &&
|
||||
prev.settings === next.settings
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ export default connect(
|
|||
prev.room.spotlights === next.room.spotlights &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||
prev.room.hideSelfView === next.room.hideSelfView &&
|
||||
prev.room.iframeUrl === next.room.iframeUrl &&
|
||||
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||
prev.settings.buttonControlBar === next.settings.buttonControlBar &&
|
||||
prev.settings.aspectRatio === next.settings.aspectRatio &&
|
||||
|
|
|
|||
|
|
@ -407,6 +407,7 @@ export default withRoomContext(connect(
|
|||
prev.room.selectedPeers === next.room.selectedPeers &&
|
||||
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
|
||||
prev.room.hideSelfView === next.room.hideSelfView &&
|
||||
prev.room.iframeUrl === next.room.iframeUrl &&
|
||||
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
|
||||
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
|
||||
prev.settings.aspectRatio === next.settings.aspectRatio &&
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
|
|||
import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
|
||||
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
|
||||
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
|
||||
import Iframe from 'react-iframe';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import FullscreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import ReplayIcon from '@material-ui/icons/Replay';
|
||||
import { AudioAnalyzer } from './AudioAnalyzer';
|
||||
|
||||
const logger = new Logger('VideoView');
|
||||
|
|
@ -172,6 +176,27 @@ const styles = (theme) =>
|
|||
{
|
||||
backgroundColor : 'rgb(174, 255, 0, 0.25)'
|
||||
}
|
||||
},
|
||||
iframeContainer :
|
||||
{
|
||||
display : 'flex',
|
||||
flexFlow : 'column',
|
||||
height : '100%',
|
||||
background : 'white'
|
||||
},
|
||||
iframeMenu :
|
||||
{
|
||||
background : 'white'
|
||||
},
|
||||
iframeMain :
|
||||
{
|
||||
flex : 1,
|
||||
overflow : 'auto',
|
||||
border : 0
|
||||
},
|
||||
iframeButtons :
|
||||
{
|
||||
padding : theme.spacing(1)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -206,6 +231,8 @@ class VideoView extends React.PureComponent
|
|||
{
|
||||
const {
|
||||
isMe,
|
||||
isIframe,
|
||||
iframeUrl,
|
||||
isMirrored,
|
||||
isScreen,
|
||||
isExtraVideo,
|
||||
|
|
@ -305,6 +332,7 @@ class VideoView extends React.PureComponent
|
|||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{ !isIframe &&
|
||||
<div className={classes.info}>
|
||||
<div className={classes.media}>
|
||||
{(audioCodec || videoCodec) &&
|
||||
|
|
@ -448,7 +476,8 @@ class VideoView extends React.PureComponent
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
{!isIframe &&
|
||||
<video
|
||||
ref='videoElement'
|
||||
className={classnames(classes.video, {
|
||||
|
|
@ -473,7 +502,60 @@ class VideoView extends React.PureComponent
|
|||
muted
|
||||
controls={false}
|
||||
/>
|
||||
}
|
||||
{isIframe &&
|
||||
<div className={classnames(classes.video, {
|
||||
contain : videoContain
|
||||
})}
|
||||
>
|
||||
<div className={classes.iframeContainer}>
|
||||
<div className={classes.iframeMenu}>
|
||||
<IconButton
|
||||
className={classes.iframeButtons}
|
||||
onClick={() =>
|
||||
{
|
||||
const elem = document.getElementById('iframe_iframe');
|
||||
|
||||
if (elem.requestFullscreen)
|
||||
{
|
||||
elem.requestFullscreen();
|
||||
}
|
||||
else if (elem.mozRequestFullScreen)
|
||||
{ /* Firefox */
|
||||
elem.mozRequestFullScreen();
|
||||
}
|
||||
else if (elem.webkitRequestFullscreen)
|
||||
{ /* Chrome, Safari and Opera */
|
||||
elem.webkitRequestFullscreen();
|
||||
}
|
||||
else if (elem.msRequestFullscreen)
|
||||
{ /* IE/Edge */
|
||||
elem.msRequestFullscreen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FullscreenIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
className={classes.iframeButtons}
|
||||
onClick={() =>
|
||||
{
|
||||
const elem = document.getElementById('iframe_iframe');
|
||||
|
||||
elem.src = iframeUrl;
|
||||
}}
|
||||
>
|
||||
<ReplayIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Iframe
|
||||
url={iframeUrl}
|
||||
id='iframe_iframe'
|
||||
className={classes.iframeMain}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -481,6 +563,8 @@ class VideoView extends React.PureComponent
|
|||
|
||||
componentDidMount()
|
||||
{
|
||||
if (this.props.isIframe) return;
|
||||
|
||||
const { videoTrack, audioTrack, showAudioAnalyzer } = this.props;
|
||||
|
||||
this._setTracks(videoTrack);
|
||||
|
|
@ -499,6 +583,8 @@ class VideoView extends React.PureComponent
|
|||
|
||||
componentWillUnmount()
|
||||
{
|
||||
if (this.props.isIframe) return;
|
||||
|
||||
clearInterval(this._videoResolutionTimer);
|
||||
|
||||
const { videoElement } = this.refs;
|
||||
|
|
@ -519,6 +605,8 @@ class VideoView extends React.PureComponent
|
|||
|
||||
componentDidUpdate(prevProps)
|
||||
{
|
||||
if (this.props.isIframe) return;
|
||||
|
||||
if (prevProps !== this.props)
|
||||
{
|
||||
const { videoTrack, audioTrack, showAudioAnalyzer } = this.props;
|
||||
|
|
@ -636,6 +724,8 @@ VideoView.propTypes =
|
|||
isMe : PropTypes.bool,
|
||||
isMirrored : PropTypes.bool,
|
||||
isScreen : PropTypes.bool,
|
||||
isIframe : PropTypes.bool,
|
||||
iframeUrl : PropTypes.string,
|
||||
isExtraVideo : PropTypes.bool,
|
||||
showQuality : PropTypes.bool,
|
||||
showAudioAnalyzer : PropTypes.bool,
|
||||
|
|
|
|||
|
|
@ -142,6 +142,23 @@ export const addSelectedPeer = (peerId) =>
|
|||
payload : { peerId }
|
||||
});
|
||||
|
||||
export const openIframe = (iframeUrl) =>
|
||||
({
|
||||
type : 'OPEN_IFRAME',
|
||||
payload : { iframeUrl }
|
||||
});
|
||||
|
||||
export const setToggleIframeInProgress = (flag) =>
|
||||
({
|
||||
type : 'SET_TOGGLE_IFRAME_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const closeIframe = () =>
|
||||
({
|
||||
type : 'CLOSE_IFRAME'
|
||||
});
|
||||
|
||||
export const removeSelectedPeer = (peerId) =>
|
||||
({
|
||||
type : 'REMOVE_SELECTED_PEER',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ const initialState =
|
|||
settingsOpen : false,
|
||||
extraVideoOpen : false,
|
||||
hideSelfView : false,
|
||||
iframeUrl : null,
|
||||
toggleIframeInProgress : false,
|
||||
rolesManagerOpen : false,
|
||||
helpOpen : false,
|
||||
aboutOpen : false,
|
||||
|
|
@ -309,6 +311,23 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, hideSelfView };
|
||||
}
|
||||
|
||||
case 'OPEN_IFRAME':
|
||||
{
|
||||
const { iframeUrl } = action.payload;
|
||||
|
||||
return { ...state, iframeUrl };
|
||||
}
|
||||
|
||||
case 'CLOSE_IFRAME':
|
||||
{
|
||||
const iframeUrl = null;
|
||||
|
||||
return { ...state, iframeUrl };
|
||||
}
|
||||
|
||||
case 'SET_TOGGLE_IFRAME_IN_PROGRESS':
|
||||
return { ...state, toggleIframeInProgress: action.payload.flag };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ const peersKeySelector = createSelector(
|
|||
(peers) => Object.keys(peers)
|
||||
);
|
||||
|
||||
export const showIframeSelect = (state) => state.room.iframeUrl;
|
||||
|
||||
export const peersValueSelector = createSelector(
|
||||
peersSelector,
|
||||
(peers) => Object.values(peers)
|
||||
|
|
@ -186,6 +188,7 @@ export const raisedHandsSelector = createSelector(
|
|||
|
||||
export const videoBoxesSelector = createSelector(
|
||||
isHiddenSelect,
|
||||
showIframeSelect,
|
||||
spotlightsLengthSelector,
|
||||
screenProducersSelector,
|
||||
spotlightScreenConsumerSelector,
|
||||
|
|
@ -193,6 +196,7 @@ export const videoBoxesSelector = createSelector(
|
|||
spotlightExtraVideoConsumerSelector,
|
||||
(
|
||||
isHidden,
|
||||
iframeUrl,
|
||||
spotlightsLength,
|
||||
screenProducers,
|
||||
screenConsumers,
|
||||
|
|
@ -202,7 +206,8 @@ export const videoBoxesSelector = createSelector(
|
|||
{
|
||||
return spotlightsLength + (isHidden ? 0 : 1) +
|
||||
(isHidden ? 0 : screenProducers.length) + screenConsumers.length +
|
||||
(isHidden ? 0 : extraVideoProducers.length) + extraVideoConsumers.length;
|
||||
(isHidden ? 0 : extraVideoProducers.length) + extraVideoConsumers.length +
|
||||
(isHidden ? 0 : (iframeUrl ? 1 : 0));
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -262,6 +262,8 @@ class Room extends EventEmitter
|
|||
|
||||
this._fileHistory = [];
|
||||
|
||||
this._iframeHistory = null;
|
||||
|
||||
this._lastN = [];
|
||||
|
||||
this._peers = {};
|
||||
|
|
@ -310,6 +312,8 @@ class Room extends EventEmitter
|
|||
|
||||
this._fileHistory = null;
|
||||
|
||||
this._iframeHistory = null;
|
||||
|
||||
this._lobby.close();
|
||||
|
||||
this._lobby = null;
|
||||
|
|
@ -923,6 +927,7 @@ class Room extends EventEmitter
|
|||
chatHistory : this._chatHistory,
|
||||
fileHistory : this._fileHistory,
|
||||
lastNHistory : this._lastN,
|
||||
iframeHistory : this._iframeHistory,
|
||||
locked : this._locked,
|
||||
lobbyPeers : lobbyPeers,
|
||||
accessCode : this._accessCode
|
||||
|
|
@ -1770,6 +1775,57 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'toggleIframe':
|
||||
{
|
||||
if (!peer.joined)
|
||||
throw new Error('Peer not yet joined');
|
||||
|
||||
if (!this._hasPermission(peer, SHARE_SCREEN))
|
||||
throw new Error('Peer not authorized to toggle external app');
|
||||
|
||||
const { iframeUrl } = request.data;
|
||||
|
||||
if (iframeUrl && this._iframeHistory)
|
||||
throw new Error('External app already opened');
|
||||
|
||||
if (!iframeUrl)
|
||||
{
|
||||
this._iframeHistory = null;
|
||||
|
||||
// Spread to others and self
|
||||
this._notification(peer.socket, 'closeIframe', null, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const pattern = new RegExp(
|
||||
'^(https?:\\/\\/)?' +
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' +
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?' +
|
||||
'(\\#[-a-z\\d_]*)?$',
|
||||
'i'
|
||||
);
|
||||
|
||||
if (!pattern.test(iframeUrl))
|
||||
throw new Error('Not a valid url for external app');
|
||||
|
||||
if (!iframeUrl.toLowerCase().startsWith('https://'))
|
||||
throw new Error('Only https allowed for external apps');
|
||||
|
||||
this._iframeHistory = iframeUrl;
|
||||
|
||||
// Spread to others and self
|
||||
this._notification(peer.socket, 'showIframe',
|
||||
{ iframeUrl }, true, true);
|
||||
}
|
||||
// Return no error
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopAllScreenSharing':
|
||||
{
|
||||
if (!this._hasPermission(peer, MODERATE_ROOM))
|
||||
|
|
|
|||
|
|
@ -27,24 +27,25 @@
|
|||
"axios": "^0.21.1",
|
||||
"base-64": "^0.1.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"body-parser": "^1.20.1",
|
||||
"colors": "^1.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"connect-redis": "^4.0.3",
|
||||
"convict": "^6.1.0",
|
||||
"convict": "^6.2.3",
|
||||
"convict-format-with-validator": "^6.0.1",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"debug": "^4.3.1",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"express-socket.io-session": "^1.3.5",
|
||||
"fast-stats": "^0.0.6",
|
||||
"helmet": "^3.21.2",
|
||||
"ims-lti": "^3.0.2",
|
||||
"json5": "^2.2.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mediasoup": "3.10.5",
|
||||
"openid-client": "^3.7.3",
|
||||
"json5": "^2.2.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mediasoup": "3.12.12",
|
||||
"openid-client": "^5.3.1",
|
||||
"os-utils": "^0.0.14",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-lti": "0.0.7",
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
"pidusage": "^2.0.21",
|
||||
"prom-client": "^13.1.0",
|
||||
"redis": "v3",
|
||||
"socket.io": "^2.4.0",
|
||||
"socket.io": "^4.5.4",
|
||||
"spdy": "^4.0.2",
|
||||
"toml": "^3.0.0",
|
||||
"uuid": "^7.0.2",
|
||||
|
|
@ -75,7 +76,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^4.21.0",
|
||||
"@typescript-eslint/parser": "^4.21.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue