Added support for locking rooms

master
Håvar Aambø Fosstveit 2019-02-15 12:23:05 +01:00
parent 4fd3c6c8b1
commit 1c5f90cff6
11 changed files with 331 additions and 108 deletions

View File

@ -990,10 +990,22 @@ export default class RoomClient
this._signalingSocket.on('connect', () => this._signalingSocket.on('connect', () =>
{ {
logger.debug('signaling Peer "connect" event'); logger.debug('signaling Peer "connect" event');
});
this._signalingSocket.on('room-ready', () =>
{
logger.debug('signaling Peer "room-ready" event');
this._joinRoom({ displayName, device }); this._joinRoom({ displayName, device });
}); });
this._signalingSocket.on('room-locked', () =>
{
logger.debug('signaling Peer "room-locked" event');
store.dispatch(stateActions.setRoomLockedOut());
});
this._signalingSocket.on('disconnect', () => this._signalingSocket.on('disconnect', () =>
{ {
logger.warn('signaling Peer "disconnect" event'); logger.warn('signaling Peer "disconnect" event');
@ -1024,10 +1036,34 @@ export default class RoomClient
this._room.receiveNotification(notification); this._room.receiveNotification(notification);
}); });
this._signalingSocket.on('active-speaker', (data) => this._signalingSocket.on('lock-room', ({ peerName }) =>
{ {
const { peerName } = data; store.dispatch(
stateActions.setRoomLocked());
const peer = this._room.getPeerByName(peerName);
if (peer)
{
this.notify(`${peer.appData.displayName} locked the room.`);
}
});
this._signalingSocket.on('unlock-room', ({ peerName }) =>
{
store.dispatch(
stateActions.setRoomUnLocked());
const peer = this._room.getPeerByName(peerName);
if (peer)
{
this.notify(`${peer.appData.displayName} unlocked the room.`);
}
});
this._signalingSocket.on('active-speaker', ({ peerName }) =>
{
store.dispatch( store.dispatch(
stateActions.setRoomActiveSpeaker(peerName)); stateActions.setRoomActiveSpeaker(peerName));
@ -1035,11 +1071,8 @@ export default class RoomClient
this._spotlights.handleActiveSpeaker(peerName); this._spotlights.handleActiveSpeaker(peerName);
}); });
this._signalingSocket.on('display-name-changed', (data) => this._signalingSocket.on('display-name-changed', ({ peerName, displayName: name }) =>
{ {
// eslint-disable-next-line no-shadow
const { peerName, displayName } = data;
// NOTE: Hack, we shouldn't do this, but this is just a demo. // NOTE: Hack, we shouldn't do this, but this is just a demo.
const peer = this._room.getPeerByName(peerName); const peer = this._room.getPeerByName(peerName);
@ -1050,20 +1083,18 @@ export default class RoomClient
return; return;
} }
const oldDisplayName = peer.appData.displayName; const oldDisplayName = peer.appData.name;
peer.appData.displayName = displayName; peer.appData.displayName = name;
store.dispatch( store.dispatch(
stateActions.setPeerDisplayName(displayName, peerName)); stateActions.setPeerDisplayName(name, peerName));
this.notify(`${oldDisplayName} changed their display name to ${displayName}.`); this.notify(`${oldDisplayName} changed their display name to ${name}.`);
}); });
this._signalingSocket.on('profile-picture-changed', (data) => this._signalingSocket.on('profile-picture-changed', ({ peerName, picture }) =>
{ {
const { peerName, picture } = data;
store.dispatch(stateActions.setPeerPicture(peerName, picture)); store.dispatch(stateActions.setPeerPicture(peerName, picture));
}); });
@ -1292,6 +1323,42 @@ export default class RoomClient
} }
} }
async lockRoom()
{
logger.debug('lockRoom()');
try
{
await this.sendRequest('lock-room');
store.dispatch(
stateActions.setRoomLocked());
this.notify('You locked the room.');
}
catch (error)
{
logger.error('lockRoom() | failed: %o', error);
}
}
async unlockRoom()
{
logger.debug('unlockRoom()');
try
{
await this.sendRequest('unlock-room');
store.dispatch(
stateActions.setRoomUnLocked());
this.notify('You unlocked the room.');
}
catch (error)
{
logger.error('unlockRoom() | failed: %o', error);
}
}
async _setMicProducer() async _setMicProducer()
{ {
if (!this._room.canSend('audio')) if (!this._room.canSend('audio'))

View File

@ -59,11 +59,13 @@ class Sidebar extends Component
roomClient, roomClient,
toolbarsVisible, toolbarsVisible,
me, me,
screenProducer screenProducer,
locked
} = this.props; } = this.props;
let screenState; let screenState;
let screenTip; let screenTip;
let lockState = 'unlocked';
if (me.needExtension) if (me.needExtension)
{ {
@ -86,6 +88,11 @@ class Sidebar extends Component
screenTip = 'Start screen sharing'; screenTip = 'Start screen sharing';
} }
if (locked)
{
lockState = 'locked';
}
return ( return (
<div <div
className={classnames('sidebar room-controls', { className={classnames('sidebar room-controls', {
@ -161,6 +168,25 @@ class Sidebar extends Component
</Otherwise> </Otherwise>
</Choose> </Choose>
</If> </If>
<div
className={classnames('button', 'lock', lockState, {
on : locked
})}
data-tip={`Room is ${lockState}`}
data-place='right'
data-type='dark'
onClick={() =>
{
if (locked)
{
roomClient.unlockRoom();
}
else
{
roomClient.lockRoom();
}
}}
/>
<div <div
className={classnames('button', 'raise-hand', { className={classnames('button', 'raise-hand', {
on : me.raiseHand, on : me.raiseHand,
@ -188,7 +214,8 @@ Sidebar.propTypes = {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
toolbarsVisible : PropTypes.bool.isRequired, toolbarsVisible : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired, me : appPropTypes.Me.isRequired,
screenProducer : appPropTypes.Producer screenProducer : appPropTypes.Producer,
locked : PropTypes.bool.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
@ -196,7 +223,8 @@ const mapStateToProps = (state) =>
toolbarsVisible : state.room.toolbarsVisible, toolbarsVisible : state.room.toolbarsVisible,
screenProducer : Object.values(state.producers) screenProducer : Object.values(state.producers)
.find((producer) => producer.source === 'screen'), .find((producer) => producer.source === 'screen'),
me : state.me me : state.me,
locked : state.room.locked
}); });
export default withRoomContext(connect( export default withRoomContext(connect(

View File

@ -73,97 +73,114 @@ class Room extends React.Component
democratic : Peers democratic : Peers
}[room.mode]; }[room.mode];
return ( if (room.lockedOut)
<Fragment> {
<Appear duration={300}> return (
<div data-component='Room'> <Fragment>
<CookieConsent> <Appear duration={300}>
This website uses cookies to enhance the user experience. <div data-component='Room'>
</CookieConsent> <div className='locked-out'>
This room is locked at the moment, try again later.
<FullScreenView advancedMode={room.advancedMode} />
<VideoWindow advancedMode={room.advancedMode} />
<div className='room-wrapper'>
<div data-component='Logo' />
<AudioPeers />
<Notifications />
<If condition={room.advancedMode}>
<div className='state' data-tip='Server status'>
<div className={classnames('icon', room.state)} />
<p className={classnames('text', room.state)}>{room.state}</p>
</div>
</If>
<div
className={classnames('room-link-wrapper room-controls', {
'visible' : this.props.room.toolbarsVisible
})}
>
<div className='room-link'>
<CopyToClipboard
text={room.url}
onCopy={onRoomLinkCopy}
>
<a
className='link'
href={room.url}
target='_blank'
data-tip='Click to copy room link'
rel='noopener noreferrer'
onClick={(event) =>
{
// If this is a 'Open in new window/tab' don't prevent
// click default action.
if (
event.ctrlKey || event.shiftKey || event.metaKey ||
// Middle click (IE > 9 and everyone else).
(event.button && event.button === 1)
)
{
return;
}
event.preventDefault();
}}
>
invitation link
</a>
</CopyToClipboard>
</div>
</div> </div>
</div>
<View advancedMode={room.advancedMode} /> </Appear>
</Fragment>
<Draggable handle='.me-container' bounds='body' cancel='.display-name'> );
}
else
{
return (
<Fragment>
<Appear duration={300}>
<div data-component='Room'>
<CookieConsent>
This website uses cookies to enhance the user experience.
</CookieConsent>
<FullScreenView advancedMode={room.advancedMode} />
<VideoWindow advancedMode={room.advancedMode} />
<div className='room-wrapper'>
<div data-component='Logo' />
<AudioPeers />
<Notifications />
<If condition={room.advancedMode}>
<div className='state' data-tip='Server status'>
<div className={classnames('icon', room.state)} />
<p className={classnames('text', room.state)}>{room.state}</p>
</div>
</If>
<div <div
className={classnames('me-container', { className={classnames('room-link-wrapper room-controls', {
'active-speaker' : amActiveSpeaker 'visible' : this.props.room.toolbarsVisible
})} })}
> >
<Me <div className='room-link'>
advancedMode={room.advancedMode} <CopyToClipboard
/> text={room.url}
onCopy={onRoomLinkCopy}
>
<a
className='link'
href={room.url}
target='_blank'
data-tip='Click to copy room link'
rel='noopener noreferrer'
onClick={(event) =>
{
// If this is a 'Open in new window/tab' don't prevent
// click default action.
if (
event.ctrlKey || event.shiftKey || event.metaKey ||
// Middle click (IE > 9 and everyone else).
(event.button && event.button === 1)
)
{
return;
}
event.preventDefault();
}}
>
invitation link
</a>
</CopyToClipboard>
</div>
</div> </div>
</Draggable>
<View advancedMode={room.advancedMode} />
<Sidebar />
<Draggable handle='.me-container' bounds='body' cancel='.display-name'>
<ReactTooltip <div
effect='solid' className={classnames('me-container', {
delayShow={100} 'active-speaker' : amActiveSpeaker
delayHide={100} })}
/> >
<Me
advancedMode={room.advancedMode}
/>
</div>
</Draggable>
<Sidebar />
<ReactTooltip
effect='solid'
delayShow={100}
delayHide={100}
/>
</div>
<ToolArea />
</div> </div>
</Appear>
<ToolArea /> </Fragment>
</div> );
</Appear> }
</Fragment>
);
} }
} }

View File

@ -2,6 +2,8 @@ const initialState =
{ {
url : null, url : null,
state : 'new', // new/connecting/connected/disconnected/closed, state : 'new', // new/connecting/connected/disconnected/closed,
locked : false,
lockedOut : false,
activeSpeakerName : null, activeSpeakerName : null,
torrentSupport : false, torrentSupport : false,
showSettings : false, showSettings : false,
@ -35,6 +37,21 @@ const room = (state = initialState, action) =>
return { ...state, state: roomState, activeSpeakerName: null }; return { ...state, state: roomState, activeSpeakerName: null };
} }
case 'SET_ROOM_LOCKED':
{
return { ...state, locked: true };
}
case 'SET_ROOM_UNLOCKED':
{
return { ...state, locked: false };
}
case 'SET_ROOM_LOCKED_OUT':
{
return { ...state, lockedOut: true };
}
case 'SET_ROOM_ACTIVE_SPEAKER': case 'SET_ROOM_ACTIVE_SPEAKER':
{ {
const { peerName } = action.payload; const { peerName } = action.payload;

View File

@ -22,6 +22,27 @@ export const setRoomActiveSpeaker = (peerName) =>
}; };
}; };
export const setRoomLocked = () =>
{
return {
type : 'SET_ROOM_LOCKED'
};
};
export const setRoomUnLocked = () =>
{
return {
type : 'SET_ROOM_UNLOCKED'
};
};
export const setRoomLockedOut = () =>
{
return {
type : 'SET_ROOM_LOCKED_OUT'
};
};
export const setMe = ({ peerName, displayName, displayNameSet, device, loginEnabled }) => export const setMe = ({ peerName, displayName, displayNameSet, device, loginEnabled }) =>
{ {
return { return {

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
<path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9 8 4 8.9 4 10 L 4 20 C 4 21.1 4.9 22 6 22 L 18 22 C 19.1 22 20 21.1 20 20 L 20 10 C 20 8.9 19.1 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 12 13 C 13.1 13 14 13.9 14 15 C 14 16.1 13.1 17 12 17 C 10.9 17 10 16.1 10 15 C 10 13.9 10.9 13 12 13 z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px">
<path style="line-height:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;block-progression:tb;isolation:auto;mix-blend-mode:normal" d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9 8 4 8.9 4 10 L 4 20 C 4 21.1 4.9 22 6 22 L 18 22 C 19.1 22 20 21.1 20 20 L 20 10 C 20 8.9 19.1 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 13 C 13.1 13 14 13.9 14 15 C 14 16.1 13.1 17 12 17 C 10.9 17 10 16.1 10 15 C 10 13.9 10.9 13 12 13 z" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 867 B

View File

@ -1,6 +1,20 @@
[data-component='Room'] { [data-component='Room'] {
AppearFadeIn(300ms); AppearFadeIn(300ms);
> .locked-out {
position: fixed;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
width: 30vw;
text-align: center;
font-family: 'Roboto';
font-size: 2.5em;
box-shadow: 0px 5px 12px 2px rgba(17, 17, 17, 0.5);
background-color: #fff;
padding: 2vmin;
}
> .room-wrapper { > .room-wrapper {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -96,6 +96,16 @@
} }
} }
&.lock {
&.locked {
background-image: url('/resources/images/icon_locked_white.svg');
}
&.unlocked {
background-image: url('/resources/images/icon_unlocked_white.svg');
}
}
&.raise-hand { &.raise-hand {
background-image: url('/resources/images/icon-hand-white.svg'); background-image: url('/resources/images/icon-hand-white.svg');

View File

@ -25,6 +25,9 @@ class Room extends EventEmitter
// Closed flag. // Closed flag.
this._closed = false; this._closed = false;
// Locked flag.
this._locked = false;
this._chatHistory = []; this._chatHistory = [];
this._fileHistory = []; this._fileHistory = [];
@ -102,6 +105,7 @@ class Room extends EventEmitter
{ {
logger.info('handleConnection() [peerName:"%s"]', peerName); logger.info('handleConnection() [peerName:"%s"]', peerName);
// This will allow reconnects to join despite lock
if (this._signalingPeers.has(peerName)) if (this._signalingPeers.has(peerName))
{ {
logger.warn( logger.warn(
@ -114,6 +118,11 @@ class Room extends EventEmitter
signalingPeer.socket.disconnect(); signalingPeer.socket.disconnect();
this._signalingPeers.delete(peerName); this._signalingPeers.delete(peerName);
} }
else if (this._locked) // Don't allow connections to a locked room
{
socket.emit('room-locked');
socket.disconnect(true);
}
const signalingPeer = { peerName : peerName, socket : socket }; const signalingPeer = { peerName : peerName, socket : socket };
@ -127,6 +136,7 @@ class Room extends EventEmitter
this._signalingPeers.set(peerName, signalingPeer); this._signalingPeers.set(peerName, signalingPeer);
this._handleSignalingPeer(signalingPeer); this._handleSignalingPeer(signalingPeer);
socket.emit('room-ready');
} }
authCallback(data) authCallback(data)
@ -308,6 +318,38 @@ class Room extends EventEmitter
); );
}); });
signalingPeer.socket.on('lock-room', (request, cb) =>
{
// Return no error
cb(null);
this._locked = true;
// Spread to others
signalingPeer.socket.broadcast.to(this._roomId).emit(
'lock-room',
{
peerName : signalingPeer.peerName
}
);
});
signalingPeer.socket.on('unlock-room', (request, cb) =>
{
// Return no error
cb(null);
this._locked = false;
// Spread to others
signalingPeer.socket.broadcast.to(this._roomId).emit(
'unlock-room',
{
peerName : signalingPeer.peerName
}
);
});
signalingPeer.socket.on('send-file', (request, cb) => signalingPeer.socket.on('send-file', (request, cb) =>
{ {
// Return no error // Return no error

View File

@ -10,7 +10,6 @@ const https = require('https');
const http = require('http'); const http = require('http');
const express = require('express'); const express = require('express');
const compression = require('compression'); const compression = require('compression');
const url = require('url');
const Logger = require('./lib/Logger'); const Logger = require('./lib/Logger');
const Room = require('./lib/Room'); const Room = require('./lib/Room');
const Dataporten = require('passport-dataporten'); const Dataporten = require('passport-dataporten');
@ -46,12 +45,12 @@ const dataporten = new Dataporten.Setup(config.oauth2);
app.all('*', (req, res, next) => app.all('*', (req, res, next) =>
{ {
if(req.secure) if (req.secure)
{ {
return next(); return next();
} }
res.redirect('https://' + req.hostname + req.url); res.redirect(`https://${req.hostname}${req.url}`);
}); });
app.use(dataporten.passport.initialize()); app.use(dataporten.passport.initialize());
@ -71,10 +70,10 @@ app.get('/login', (req, res, next) =>
dataporten.setupLogout(app, '/logout'); dataporten.setupLogout(app, '/logout');
app.get('/', function (req, res) { app.get('/', (req, res) =>
console.log(req.url); {
res.sendFile(`${__dirname}/public/chooseRoom.html`); res.sendFile(`${__dirname}/public/chooseRoom.html`);
}) });
app.get( app.get(
'/auth-callback', '/auth-callback',