Add support for moderating rooms. Kick user, mute all users, stop all videos.
parent
c70740f5c7
commit
7f2f27b858
|
|
@ -1126,6 +1126,66 @@ export default class RoomClient
|
|||
lobbyPeerActions.setLobbyPeerPromotionInProgress(peerId, false));
|
||||
}
|
||||
|
||||
async kickPeer(peerId)
|
||||
{
|
||||
logger.debug('kickPeer() [peerId:"%s"]', peerId);
|
||||
|
||||
store.dispatch(
|
||||
peerActions.setPeerKickInProgress(peerId, true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('moderator:kickPeer', { peerId });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('kickPeer() failed: %o', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
peerActions.setPeerKickInProgress(peerId, false));
|
||||
}
|
||||
|
||||
async muteAllPeers()
|
||||
{
|
||||
logger.debug('muteAllPeers()');
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setMuteAllInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('moderator:muteAll');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('muteAllPeers() failed: %o', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setMuteAllInProgress(false));
|
||||
}
|
||||
|
||||
async stopAllPeerVideo()
|
||||
{
|
||||
logger.debug('stopAllPeerVideo()');
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setStopAllVideoInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest('moderator:stopAllVideo');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('stopAllPeerVideo() failed: %o', error);
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setStopAllVideoInProgress(false));
|
||||
}
|
||||
|
||||
// type: mic/webcam/screen
|
||||
// mute: true/false
|
||||
async modifyPeerConsumer(peerId, type, mute)
|
||||
|
|
@ -1902,10 +1962,10 @@ export default class RoomClient
|
|||
|
||||
case 'newPeer':
|
||||
{
|
||||
const { id, displayName, picture } = notification.data;
|
||||
const { id, displayName, picture, roles } = notification.data;
|
||||
|
||||
store.dispatch(
|
||||
peerActions.addPeer({ id, displayName, picture, consumers: [] }));
|
||||
peerActions.addPeer({ id, displayName, picture, roles, consumers: [] }));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
|
|
@ -2004,6 +2064,96 @@ export default class RoomClient
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:mute':
|
||||
{
|
||||
// const { peerId } = notification.data;
|
||||
|
||||
if (this._micProducer && !this._micProducer.paused)
|
||||
{
|
||||
this.muteMic();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'moderator.mute',
|
||||
defaultMessage : 'Moderator muted your microphone'
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopVideo':
|
||||
{
|
||||
// const { peerId } = notification.data;
|
||||
|
||||
this.disableWebcam();
|
||||
this.disableScreenSharing();
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'moderator.mute',
|
||||
defaultMessage : 'Moderator stopped your video'
|
||||
})
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:kick':
|
||||
{
|
||||
// Need some feedback
|
||||
this.close();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'gotRole':
|
||||
{
|
||||
const { peerId, role } = notification.data;
|
||||
|
||||
if (peerId === this._peerId)
|
||||
{
|
||||
store.dispatch(meActions.addRole({ role }));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'roles.gotRole',
|
||||
defaultMessage : `You got the role: ${role}`
|
||||
})
|
||||
}));
|
||||
}
|
||||
else
|
||||
store.dispatch(peerActions.addPeerRole({ peerId, role }));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'lostRole':
|
||||
{
|
||||
const { peerId, role } = notification.data;
|
||||
|
||||
if (peerId === this._peerId)
|
||||
{
|
||||
store.dispatch(meActions.removeRole({ role }));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'roles.lostRole',
|
||||
defaultMessage : `You lost the role: ${role}`
|
||||
})
|
||||
}));
|
||||
}
|
||||
else
|
||||
store.dispatch(peerActions.removePeerRole({ peerId, role }));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
|
|
@ -2158,7 +2308,7 @@ export default class RoomClient
|
|||
canShareFiles : this._torrentSupport
|
||||
}));
|
||||
|
||||
const { peers } = await this.sendRequest(
|
||||
const { roles, peers } = await this.sendRequest(
|
||||
'join',
|
||||
{
|
||||
displayName : displayName,
|
||||
|
|
@ -2166,7 +2316,25 @@ export default class RoomClient
|
|||
rtpCapabilities : this._mediasoupDevice.rtpCapabilities
|
||||
});
|
||||
|
||||
logger.debug('_joinRoom() joined, got peers [peers:"%o"]', peers);
|
||||
logger.debug('_joinRoom() joined [peers:"%o", roles:"%o"]', peers, roles);
|
||||
|
||||
const myRoles = store.getState().me.roles;
|
||||
|
||||
for (const role of roles)
|
||||
{
|
||||
if (!myRoles.includes(role))
|
||||
{
|
||||
store.dispatch(meActions.addRole({ role }));
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
text : intl.formatMessage({
|
||||
id : 'roles.gotRole',
|
||||
defaultMessage : `You got the role: ${role}`
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for (const peer of peers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,18 @@ export const loggedIn = (flag) =>
|
|||
payload : { flag }
|
||||
});
|
||||
|
||||
export const addRole = ({ role }) =>
|
||||
({
|
||||
type : 'ADD_ROLE',
|
||||
payload : { role }
|
||||
});
|
||||
|
||||
export const removeRole = (role) =>
|
||||
({
|
||||
type : 'REMOVE_ROLE',
|
||||
payload : { role }
|
||||
});
|
||||
|
||||
export const setPicture = (picture) =>
|
||||
({
|
||||
type : 'SET_PICTURE',
|
||||
|
|
|
|||
|
|
@ -45,3 +45,22 @@ export const setPeerPicture = (peerId, picture) =>
|
|||
type : 'SET_PEER_PICTURE',
|
||||
payload : { peerId, picture }
|
||||
});
|
||||
|
||||
|
||||
export const addPeerRole = (peerId, role) =>
|
||||
({
|
||||
type : 'ADD_PEER_ROLE',
|
||||
payload : { peerId, role }
|
||||
});
|
||||
|
||||
export const removePeerRole = (peerId, role) =>
|
||||
({
|
||||
type : 'REMOVE_PEER_ROLE',
|
||||
payload : { peerId, role }
|
||||
});
|
||||
|
||||
export const setPeerKickInProgress = (peerId, flag) =>
|
||||
({
|
||||
type : 'SET_PEER_KICK_IN_PROGRESS',
|
||||
payload : { peerId, flag }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -109,4 +109,16 @@ export const toggleConsumerFullscreen = (consumerId) =>
|
|||
({
|
||||
type : 'TOGGLE_FULLSCREEN_CONSUMER',
|
||||
payload : { consumerId }
|
||||
});
|
||||
|
||||
export const setMuteAllInProgress = (flag) =>
|
||||
({
|
||||
type : 'MUTE_ALL_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
||||
export const setStopAllVideoInProgress = (flag) =>
|
||||
({
|
||||
type : 'STOP_ALL_VIDEO_IN_PROGRESS',
|
||||
payload : { flag }
|
||||
});
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRoomContext } from '../../../RoomContext';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
root :
|
||||
{
|
||||
padding : theme.spacing(1),
|
||||
width : '100%',
|
||||
overflow : 'hidden',
|
||||
cursor : 'auto',
|
||||
display : 'flex'
|
||||
},
|
||||
actionButtons :
|
||||
{
|
||||
display : 'flex'
|
||||
},
|
||||
divider :
|
||||
{
|
||||
marginLeft : theme.spacing(2)
|
||||
}
|
||||
});
|
||||
|
||||
const ListModerator = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
room,
|
||||
classes
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'room.muteAll',
|
||||
defaultMessage : 'Mute all'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
disabled={room.muteAllInProgress}
|
||||
onClick={() => roomClient.muteAllPeers()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.muteAll'
|
||||
defaultMessage='Mute all'
|
||||
/>
|
||||
</Button>
|
||||
<div className={classes.divider} />
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'room.stopAllVideo',
|
||||
defaultMessage : 'Stop all video'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
disabled={room.stopAllVideoInProgress}
|
||||
onClick={() => roomClient.stopAllPeerVideo()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='room.stopAllVideo'
|
||||
defaultMessage='Stop all video'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ListModerator.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
room : PropTypes.object.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
room : state.room
|
||||
});
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.room === next.room
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ListModerator)));
|
||||
|
|
@ -12,6 +12,7 @@ import MicIcon from '@material-ui/icons/Mic';
|
|||
import MicOffIcon from '@material-ui/icons/MicOff';
|
||||
import ScreenIcon from '@material-ui/icons/ScreenShare';
|
||||
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
|
||||
import ExitIcon from '@material-ui/icons/ExitToApp';
|
||||
import EmptyAvatar from '../../../images/avatar-empty.jpeg';
|
||||
import HandIcon from '../../../images/icon-hand-white.svg';
|
||||
|
||||
|
|
@ -91,40 +92,6 @@ const styles = (theme) =>
|
|||
flexDirection : 'row',
|
||||
justifyContent : 'flex-start',
|
||||
alignItems : 'center'
|
||||
},
|
||||
button :
|
||||
{
|
||||
flex : '0 0 auto',
|
||||
margin : '0.3rem',
|
||||
borderRadius : 2,
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.5)',
|
||||
cursor : 'pointer',
|
||||
transitionProperty : 'opacity, background-color',
|
||||
transitionDuration : '0.15s',
|
||||
width : 'var(--media-control-button-size)',
|
||||
height : 'var(--media-control-button-size)',
|
||||
opacity : 0.85,
|
||||
'&:hover' :
|
||||
{
|
||||
opacity : 1
|
||||
},
|
||||
'&.unsupported' :
|
||||
{
|
||||
pointerEvents : 'none'
|
||||
},
|
||||
'&.disabled' :
|
||||
{
|
||||
pointerEvents : 'none',
|
||||
backgroundColor : 'var(--media-control-botton-disabled)'
|
||||
},
|
||||
'&.on' :
|
||||
{
|
||||
backgroundColor : 'var(--media-control-botton-on)'
|
||||
},
|
||||
'&.off' :
|
||||
{
|
||||
backgroundColor : 'var(--media-control-botton-off)'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -134,6 +101,7 @@ const ListPeer = (props) =>
|
|||
|
||||
const {
|
||||
roomClient,
|
||||
isModerator,
|
||||
peer,
|
||||
micConsumer,
|
||||
screenConsumer,
|
||||
|
|
@ -185,9 +153,8 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={ screenVisible ? 'primary' : 'secondary'}
|
||||
disabled={ peer.peerScreenInProgress }
|
||||
onClick={(e) =>
|
||||
onClick={() =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
screenVisible ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'screen', false);
|
||||
|
|
@ -207,9 +174,8 @@ const ListPeer = (props) =>
|
|||
})}
|
||||
color={ micEnabled ? 'primary' : 'secondary'}
|
||||
disabled={ peer.peerAudioInProgress }
|
||||
onClick={(e) =>
|
||||
onClick={() =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
micEnabled ?
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
|
||||
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
|
||||
|
|
@ -221,6 +187,21 @@ const ListPeer = (props) =>
|
|||
<MicOffIcon />
|
||||
}
|
||||
</IconButton>
|
||||
{ isModerator &&
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.kickParticipant',
|
||||
defaultMessage : 'Kick out participant'
|
||||
})}
|
||||
disabled={ peer.peerKickInProgress }
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.kickPeer(peer.id);
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
</IconButton>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -230,6 +211,7 @@ ListPeer.propTypes =
|
|||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
peer : appPropTypes.Peer.isRequired,
|
||||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import PropTypes from 'prop-types';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import ListPeer from './ListPeer';
|
||||
import ListMe from './ListMe';
|
||||
import ListModerator from './ListModerator';
|
||||
import Volume from '../../Containers/Volume';
|
||||
import * as userRoles from '../../../reducers/userRoles';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -76,6 +78,7 @@ class ParticipantList extends React.PureComponent
|
|||
const {
|
||||
roomClient,
|
||||
advancedMode,
|
||||
isModerator,
|
||||
passivePeers,
|
||||
selectedPeerId,
|
||||
spotlightPeers,
|
||||
|
|
@ -84,6 +87,17 @@ class ParticipantList extends React.PureComponent
|
|||
|
||||
return (
|
||||
<div className={classes.root} ref={(node) => { this.node = node; }}>
|
||||
{ isModerator &&
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
id='room.moderatoractions'
|
||||
defaultMessage='Moderator actions'
|
||||
/>
|
||||
</li>
|
||||
<ListModerator />
|
||||
</ul>
|
||||
}
|
||||
<ul className={classes.list}>
|
||||
<li className={classes.listheader}>
|
||||
<FormattedMessage
|
||||
|
|
@ -108,7 +122,7 @@ class ParticipantList extends React.PureComponent
|
|||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peerId)}
|
||||
>
|
||||
<ListPeer id={peerId} advancedMode={advancedMode}>
|
||||
<ListPeer id={peerId} advancedMode={advancedMode} isModerator={isModerator}>
|
||||
<Volume small id={peerId} />
|
||||
</ListPeer>
|
||||
</li>
|
||||
|
|
@ -129,7 +143,7 @@ class ParticipantList extends React.PureComponent
|
|||
})}
|
||||
onClick={() => roomClient.setSelectedPeer(peerId)}
|
||||
>
|
||||
<ListPeer id={peerId} advancedMode={advancedMode} />
|
||||
<ListPeer id={peerId} advancedMode={advancedMode} isModerator={isModerator} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
@ -142,6 +156,7 @@ ParticipantList.propTypes =
|
|||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
isModerator : PropTypes.bool,
|
||||
passivePeers : PropTypes.array,
|
||||
selectedPeerId : PropTypes.string,
|
||||
spotlightPeers : PropTypes.array,
|
||||
|
|
@ -150,7 +165,12 @@ ParticipantList.propTypes =
|
|||
|
||||
const mapStateToProps = (state) =>
|
||||
{
|
||||
const isModerator =
|
||||
state.me.roles.includes(userRoles.MODERATOR) ||
|
||||
state.me.roles.includes(userRoles.ADMIN);
|
||||
|
||||
return {
|
||||
isModerator,
|
||||
passivePeers : passivePeersSelector(state),
|
||||
selectedPeerId : state.room.selectedPeerId,
|
||||
spotlightPeers : spotlightPeersSelector(state)
|
||||
|
|
@ -165,6 +185,7 @@ const ParticipantListContainer = withRoomContext(connect(
|
|||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me.roles === next.me.roles &&
|
||||
prev.peers === next.peers &&
|
||||
prev.room.spotlights === next.room.spotlights &&
|
||||
prev.room.selectedPeerId === next.room.selectedPeerId
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import * as userRoles from './userRoles';
|
||||
|
||||
const initialState =
|
||||
{
|
||||
id : null,
|
||||
picture : null,
|
||||
roles : [ userRoles.ALL ],
|
||||
canSendMic : false,
|
||||
canSendWebcam : false,
|
||||
canShareScreen : false,
|
||||
|
|
@ -43,6 +46,21 @@ const me = (state = initialState, action) =>
|
|||
return { ...state, loggedIn: flag };
|
||||
}
|
||||
|
||||
case 'ADD_ROLE':
|
||||
{
|
||||
const roles = [ ...state.roles, action.payload.role ];
|
||||
|
||||
return { ...state, roles };
|
||||
}
|
||||
|
||||
case 'REMOVE_ROLE':
|
||||
{
|
||||
const roles = state.roles.filter((role) =>
|
||||
role !== action.payload.role);
|
||||
|
||||
return { ...state, roles };
|
||||
}
|
||||
|
||||
case 'SET_PICTURE':
|
||||
return { ...state, picture: action.payload.picture };
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ const peer = (state = {}, action) =>
|
|||
|
||||
case 'SET_PEER_SCREEN_IN_PROGRESS':
|
||||
return { ...state, peerScreenInProgress: action.payload.flag };
|
||||
|
||||
case 'SET_PEER_KICK_IN_PROGRESS':
|
||||
return { ...state, peerKickInProgress: action.payload.flag };
|
||||
|
||||
case 'SET_PEER_RAISE_HAND_STATE':
|
||||
return { ...state, raiseHandState: action.payload.raiseHandState };
|
||||
|
|
@ -40,6 +43,21 @@ const peer = (state = {}, action) =>
|
|||
return { ...state, picture: action.payload.picture };
|
||||
}
|
||||
|
||||
case 'ADD_PEER_ROLE':
|
||||
{
|
||||
const roles = [ ...state.roles, action.payload.role ];
|
||||
|
||||
return { ...state, roles };
|
||||
}
|
||||
|
||||
case 'REMOVE_PEER_ROLE':
|
||||
{
|
||||
const roles = state.roles.filter((role) =>
|
||||
role !== action.payload.role);
|
||||
|
||||
return { ...state, roles };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
@ -71,6 +89,8 @@ const peers = (state = {}, action) =>
|
|||
case 'SET_PEER_RAISE_HAND_STATE':
|
||||
case 'SET_PEER_PICTURE':
|
||||
case 'ADD_CONSUMER':
|
||||
case 'ADD_PEER_ROLE':
|
||||
case 'REMOVE_PEER_ROLE':
|
||||
{
|
||||
const oldPeer = state[action.payload.peerId];
|
||||
|
||||
|
|
@ -82,6 +102,7 @@ const peers = (state = {}, action) =>
|
|||
return { ...state, [oldPeer.id]: peer(oldPeer, action) };
|
||||
}
|
||||
|
||||
case 'SET_PEER_KICK_IN_PROGRESS':
|
||||
case 'REMOVE_CONSUMER':
|
||||
{
|
||||
const oldPeer = state[action.payload.peerId];
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, spotlights };
|
||||
}
|
||||
|
||||
case 'MUTE_ALL_IN_PROGRESS':
|
||||
return { ...state, muteAllInProgress: action.payload.flag };
|
||||
|
||||
case 'STOP_ALL_VIDEO_IN_PROGRESS':
|
||||
return { ...state, stopAllVideoInProgress: action.payload.flag };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
export const ADMIN = 'admin';
|
||||
export const MODERATOR = 'moderator';
|
||||
export const AUTHENTICATED = 'authenticated';
|
||||
export const ALL = 'normal';
|
||||
|
|
@ -75,13 +75,13 @@ class Lobby extends EventEmitter
|
|||
if (peer)
|
||||
{
|
||||
peer.socket.removeListener('request', peer.socketRequestHandler);
|
||||
peer.removeListener('rolesChange', peer.roleChangeHandler);
|
||||
peer.removeListener('gotRole', peer.gotRoleHandler);
|
||||
peer.removeListener('displayNameChanged', peer.displayNameChangeHandler);
|
||||
peer.removeListener('pictureChanged', peer.pictureChangeHandler);
|
||||
peer.removeListener('close', peer.closeHandler);
|
||||
|
||||
peer.socketRequestHandler = null;
|
||||
peer.roleChangeHandler = null;
|
||||
peer.gotRoleHandler = null;
|
||||
peer.displayNameChangeHandler = null;
|
||||
peer.pictureChangeHandler = null;
|
||||
peer.closeHandler = null;
|
||||
|
|
@ -116,7 +116,7 @@ class Lobby extends EventEmitter
|
|||
});
|
||||
};
|
||||
|
||||
peer.roleChangeHandler = () =>
|
||||
peer.gotRoleHandler = () =>
|
||||
{
|
||||
logger.info('parkPeer() | rolesChange [peer:"%s"]', peer.id);
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ class Lobby extends EventEmitter
|
|||
|
||||
this._peers.set(peer.id, peer);
|
||||
|
||||
peer.on('rolesChange', peer.roleChangeHandler);
|
||||
peer.on('gotRole', peer.gotRoleHandler);
|
||||
peer.on('displayNameChanged', peer.displayNameChangeHandler);
|
||||
peer.on('pictureChanged', peer.pictureChangeHandler);
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ class Peer extends EventEmitter
|
|||
|
||||
logger.info('addRole() | [newRole:"%s]"', newRole);
|
||||
|
||||
this.emit('rolesChange', { newRole });
|
||||
this.emit('gotRole', { newRole });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ class Peer extends EventEmitter
|
|||
|
||||
logger.info('removeRole() | [oldRole:"%s]"', oldRole);
|
||||
|
||||
this.emit('rolesChange', { oldRole });
|
||||
this.emit('lostRole', { oldRole });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +302,8 @@ class Peer extends EventEmitter
|
|||
{
|
||||
id : this.id,
|
||||
displayName : this.displayName,
|
||||
picture : this.picture
|
||||
picture : this.picture,
|
||||
roles : this.roles
|
||||
};
|
||||
|
||||
return peerInfo;
|
||||
|
|
|
|||
|
|
@ -420,6 +420,32 @@ class Room extends EventEmitter
|
|||
picture : peer.picture
|
||||
}, true);
|
||||
});
|
||||
|
||||
peer.on('gotRole', ({ newRole }) =>
|
||||
{
|
||||
// Ensure the Peer is joined.
|
||||
if (!peer.joined)
|
||||
return;
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'gotRole', {
|
||||
peerId : peer.id,
|
||||
role : newRole
|
||||
}, true);
|
||||
});
|
||||
|
||||
peer.on('lostRole', ({ oldRole }) =>
|
||||
{
|
||||
// Ensure the Peer is joined.
|
||||
if (!peer.joined)
|
||||
return;
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'lostRole', {
|
||||
peerId : peer.id,
|
||||
role : oldRole
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
async _handleSocketRequest(peer, request, cb)
|
||||
|
|
@ -483,7 +509,10 @@ class Room extends EventEmitter
|
|||
.filter((joinedPeer) => joinedPeer.id !== peer.id)
|
||||
.map((joinedPeer) => (joinedPeer.peerInfo));
|
||||
|
||||
cb(null, { peers: peerInfos });
|
||||
cb(null, {
|
||||
roles : peer.roles,
|
||||
peers : peerInfos
|
||||
});
|
||||
|
||||
// Mark the new Peer as joined.
|
||||
peer.joined = true;
|
||||
|
|
@ -511,7 +540,8 @@ class Room extends EventEmitter
|
|||
{
|
||||
id : peer.id,
|
||||
displayName : displayName,
|
||||
picture : picture
|
||||
picture : picture,
|
||||
roles : peer.roles
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -1077,6 +1107,69 @@ class Room extends EventEmitter
|
|||
break;
|
||||
}
|
||||
|
||||
case 'moderator:muteAll':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'moderator:mute', {
|
||||
peerId : peer.id
|
||||
}, true);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:stopAllVideo':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
// Spread to others
|
||||
this._notification(peer.socket, 'moderator:stopVideo', {
|
||||
peerId : peer.id
|
||||
}, true);
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'moderator:kickPeer':
|
||||
{
|
||||
if (
|
||||
!peer.hasRole(userRoles.MODERATOR) &&
|
||||
!peer.hasRole(userRoles.ADMIN)
|
||||
)
|
||||
throw new Error('peer does not have moderator priveleges');
|
||||
|
||||
const { peerId } = request.data;
|
||||
|
||||
const kickPeer = this._peers[peerId];
|
||||
|
||||
if (!kickPeer)
|
||||
throw new Error(`peer with id "${peerId}" not found`);
|
||||
|
||||
this._notification(
|
||||
kickPeer.socket,
|
||||
'moderator:kick'
|
||||
);
|
||||
|
||||
kickPeer.close();
|
||||
|
||||
cb();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
logger.error('unknown request.method "%s"', request.method);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
module.exports = {
|
||||
// Allowed to enter locked rooms + all other priveleges
|
||||
ADMIN : 0,
|
||||
ADMIN : 'admin',
|
||||
// Allowed to enter restricted rooms if configured.
|
||||
// Allowed to moderate users in a room (mute all,
|
||||
// spotlight video, kick users)
|
||||
MODERATOR : 1,
|
||||
MODERATOR : 'moderator',
|
||||
// Same as MODERATOR, but can't moderate users
|
||||
AUTHENTICATED : 2,
|
||||
AUTHENTICATED : 'authenticated',
|
||||
// No priveleges
|
||||
ALL : 3
|
||||
ALL : 'normal'
|
||||
};
|
||||
Loading…
Reference in New Issue