Skip to content

Commit

Permalink
Merge pull request #215 from michaelKurowski/214-leave-room-functiona…
Browse files Browse the repository at this point in the history
…lity

#215 Leave room feature
  • Loading branch information
Marcin Grzeszczak committed Oct 19, 2019
2 parents 117eef4 + 5e5bdb5 commit a2d4313
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function mockModels() {
exec(query, fields, cb) {
return Promise.resolve({
id: 'DUMMY_ROOM',
members: ['userA', 'userB'],
members: ['userA', 'userB', 'DUMMY_USERNAME'],
save: () => Promise.resolve()
})
}
Expand Down
16 changes: 8 additions & 8 deletions src/server/.tests/mocha/ws-routes/controllers/Room.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ describe('Room websocket namespace', () => {
suite.emitSpy = sinon.spy(connection, 'emit')
connection.on(CLIENT_EVENTS.MESSAGE, data => {
suite.roomInstance.message(data, connection, suite.connectionsMock)
then()
.then(then)
})
})

Expand Down Expand Up @@ -312,7 +312,7 @@ describe('Room websocket namespace', () => {
suite.toSpy = sinon.spy(connection, 'to')
connection.on(CLIENT_EVENTS.MESSAGE, data => {
suite.roomInstance.message(data, connection, suite.connectionsMock)
then()
.then(then)
})
})

Expand Down Expand Up @@ -340,7 +340,7 @@ describe('Room websocket namespace', () => {
suite.emitSpy = sinon.spy(connection, 'emit')
connection.on(CLIENT_EVENTS.MESSAGE, data => {
suite.roomInstance.message(data, connection, suite.connectionsMock)
then()
.then(then)
})
})

Expand Down Expand Up @@ -373,7 +373,7 @@ describe('Room websocket namespace', () => {
suite.leaveSpy = sinon.spy(connection, 'leave')
connection.on(CLIENT_EVENTS.MESSAGE, data => {
suite.roomInstance.leave(data, connection, suite.connectionsMock)
then()
.then(then)
})
})

Expand All @@ -384,8 +384,7 @@ describe('Room websocket namespace', () => {

//then
function then() {
const expectedEventData = {roomId: ROOM_ID}
sinon.assert.calledWith(suite.leaveSpy.firstCall, expectedEventData)
sinon.assert.calledWith(suite.leaveSpy.firstCall, ROOM_ID)
done()
}
})
Expand Down Expand Up @@ -650,7 +649,8 @@ describe('Room websocket namespace', () => {
})

function then(data) {
const EXPECTED_USERNAMES = [USER_A_USERNAME, USER_B_USERNAME]
const DUMMY_USERNAME = 'DUMMY_USERNAME'
const EXPECTED_USERNAMES = [USER_A_USERNAME, USER_B_USERNAME, DUMMY_USERNAME]
assert.deepEqual(data.payload.usernames, EXPECTED_USERNAMES)
done()
}
Expand Down Expand Up @@ -687,7 +687,7 @@ function mockModels() {
exec(query, fields, cb) {
return Promise.resolve({
id: 'DUMMY_ROOM',
members: ['userA', 'userB'],
members: ['userA', 'userB', 'DUMMY_USERNAME'],
save: () => Promise.resolve()
})
}
Expand Down
10 changes: 8 additions & 2 deletions src/server/frontEnd/src/services/roomsManagement/room.actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ const CODES = {
ADD_MEMBER: 'ADD_MEMBER',
SEND_MESSAGE: 'SEND_MESSAGE',
SET_MEMBERS: 'SET_MEMBERS',
INCORRECT_MESSAGE: 'INCORRECT_MESSAGE'
INCORRECT_MESSAGE: 'INCORRECT_MESSAGE',
REMOVE_MEMBER: 'REMOVE_MEMBER'
}
const actions = {
addMessage,
setMembers,
addMember,
sendMessage
sendMessage,
removeMember
}

function addMember(username, roomId) {
return {type: CODES.ADD_MEMBER, payload: {username, roomId}}
}

function removeMember(username, roomId) {
return {type: CODES.REMOVE_MEMBER, payload: {username, roomId}}
}

function setMembers(members, roomId) {
return {type: CODES.SET_MEMBERS, payload: {members, roomId}}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ const CODES = {
JOIN_ROOM: 'JOIN_ROOM',
SELECT_ROOM: 'SELECT_ROOM',
LEAVE_ROOM: 'LEAVE_ROOM',
CREATE_ROOM: 'CREATE_ROOM'
CREATE_ROOM: 'CREATE_ROOM',
REMOVE_ROOM: 'REMOVE_ROOM'
}

const actions = {
joinRoom: roomId => ({type: CODES.JOIN_ROOM, payload: {roomId}}),
createRoom: invitedUsers => ({type: CODES.CREATE_ROOM, payload: {invitedUsers}}),
selectRoom: roomId => ({type: CODES.SELECT_ROOM, payload: {roomId}}),
leaveRoom: roomId => ({type: CODES.LEAVE_ROOM, payload: {roomId}})
leaveRoom: roomId => ({type: CODES.LEAVE_ROOM, payload: {roomId}}),
removeRoom: roomId => ({type: CODES.REMOVE_ROOM, payload: {roomId}})
}

module.exports = {CODES, actions}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const _ = require('lodash')

const ACTION_CODES = Object.assign(
{},
require('./room.actions').CODES,
Expand Down Expand Up @@ -25,6 +27,10 @@ const handleActions = (state = initialState, action = {}) => {
return addMessage(state, action.payload)
case ACTION_CODES.SET_MEMBERS:
return setMembers(state, action.payload)
case ACTION_CODES.REMOVE_ROOM:
return removeRoom(state, action.payload)
case ACTION_CODES.REMOVE_MEMBER:
return removeMember(state, action.payload)
default:
return state
}
Expand All @@ -34,14 +40,21 @@ function selectRoom(state, payload) {
return Object.assign({}, state, {selectedRoom: payload.roomId})
}

function removeRoom(state, payload) {
const currentRooms = state.rooms
const newRooms = Object.assign({}, currentRooms)
delete newRooms[payload.roomId]
return Object.assign({}, state, {rooms: newRooms})
}

function addMessage(state, payload) {
const newMessage = payload.message

const currentRooms = state.rooms
const currentRoom = currentRooms[payload.roomId] || roomSchema
const currentMessages = currentRoom.messages

const newMessages = [...currentMessages, newMessage]
const newMessages = _.uniqWith([...currentMessages, newMessage], _.isEqual)
const newRoom = Object.assign({}, currentRoom, {messages: newMessages})
const newRooms = Object.assign({}, currentRooms, {[payload.roomId]: newRoom})

Expand All @@ -56,6 +69,21 @@ function addMember(state, payload) {
const currentMembers = currentRoom.members

const newMembers = [...currentMembers, newMember]
const membersWithoutDuplicates = Array.from(new Set(newMembers))
const newRoom = Object.assign({}, currentRoom, {members: membersWithoutDuplicates})
const newRooms = Object.assign({}, currentRooms, {[payload.roomId]: newRoom})

return Object.assign({}, state, {rooms: newRooms})
}

function removeMember(state, payload) {
const memberToRemove = payload.username

const currentRooms = state.rooms
const currentRoom = currentRooms[payload.roomId] || roomSchema
const currentMembers = currentRoom.members

const newMembers = currentMembers.filter(username => username !== memberToRemove)
const newRoom = Object.assign({}, currentRoom, {members: newMembers})
const newRooms = Object.assign({}, currentRooms, {[payload.roomId]: newRoom})

Expand Down
10 changes: 10 additions & 0 deletions src/server/frontEnd/src/services/sagas/webSocketEmitters.saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function* watchSendMessage() {
yield takeEvery(ROOM_ACTION_CODES.SEND_MESSAGE, sendMessage)
yield takeEvery(ROOM_MANAGEMENT_CODES.JOIN_ROOM, joinRoom)
yield takeEvery(ROOM_MANAGEMENT_CODES.CREATE_ROOM, createRoom)
yield takeEvery(ROOM_MANAGEMENT_CODES.LEAVE_ROOM, leaveRoom)
yield takeEvery(SESSION_ACTION_CODES.LOG_OUT, logOut)
yield takeEvery(FIND_USERS_ACTION_CODES.FIND_USER, findUsersByUsername)
}
Expand All @@ -39,6 +40,11 @@ function* joinRoom(action) {
yield call(emitJoinRoom, socket, action.payload)
}

function* leaveRoom(action) {
const socket = webSocketProvider.get()
yield call(emitLeaveRoom, socket, action.payload)
}

function* sendMessage(action) {
const socket = webSocketProvider.get()
yield call(emitMessage, socket, action.payload.messageObject)
Expand All @@ -60,6 +66,10 @@ function emitMessage(socket, eventObject) {
socket.room.emit(protocols.MESSAGE, eventObject)
}

function emitLeaveRoom(socket, eventObject) {
socket.room.emit(protocols.LEAVE, eventObject)
}

function closeSocket(socket) {
socket.room.close()
socket.users.close()
Expand Down
10 changes: 10 additions & 0 deletions src/server/frontEnd/src/services/sagas/webSocketListener.saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ function* mapWebsocketEventsToActions(event) {
case PROTOCOL.room.eventTypes.JOIN:
yield* handleJoinEvent(eventData)
break
case PROTOCOL.room.eventTypes.LEAVE:
yield* handleLeaveEvent(eventData)
break
case PROTOCOL.users.eventTypes.FIND:
yield put(findUserActions.usersFound(eventData.payload.usernames))
return
Expand All @@ -65,6 +68,13 @@ function* mapWebsocketEventsToActions(event) {
}
}

function* handleLeaveEvent(event) {
const loggedUserUsername = yield select(store => store.sessionReducer.username)
if (loggedUserUsername === event.payload.username)
yield put(roomsManagementActions.removeRoom(event.payload.roomId))
else yield put(roomActions.removeMember(event.payload.username, event.payload.roomId))
}

function* handleJoinEvent(event) {
const loggedUserUsername = yield select(store => store.sessionReducer.username)
if (loggedUserUsername === event.payload.username)
Expand Down
5 changes: 3 additions & 2 deletions src/server/frontEnd/src/theme/scenes/chatpage/chatpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ function mapDispatchToProps(dispatch) {
joinRoom: roomId => dispatch(roomsManagementActions.actions.joinRoom(roomId)),
createRoom: invitedUsers => dispatch(roomsManagementActions.actions.createRoom(invitedUsers)),
logOut: () => dispatch(sessionActions.actions.logOut()),
findUsersByUsername: username => dispatch(findUsersActions.actions.findUser(username))
findUsersByUsername: username => dispatch(findUsersActions.actions.findUser(username)),
leaveRoom: roomId => dispatch(roomsManagementActions.actions.leaveRoom(roomId))
}
}

Expand Down Expand Up @@ -117,7 +118,7 @@ class ChatPage extends React.Component {
<SidePanel direction={SIDE_PANEL_DIRECTIONS.LEFT} color='dark'>
<Avatar size={AVATAR_SIZES.BIG} username={this.props.username} />
<RoomJoiner joinRoom={this.joinToRoom} />
<RoomsDialer rooms={this.props.joinedRooms} selectRoom={this.changeSelectedRoom} />
<RoomsDialer rooms={this.props.joinedRooms} selectRoom={this.changeSelectedRoom} leaveRoom={this.props.leaveRoom}/>
</SidePanel>
{
this.getSelectedRoom() ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class RoomsDialer extends React.Component {
name={`Room ${room}`}
ID={room}
onClick={() => this.props.selectRoom({roomId: room})}
onLeave={() => this.props.leaveRoom(room)}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ class RoomsListElement extends React.Component {
}
render() {
return(
<div className='border m-2 p-2' data-test='list-dialer-element' key={this.state.ID} onClick={this.props.onClick}>
<li>{this.state.name}</li>
<div className='border m-2 p-2 flex justify-between' >
<div data-test='list-dialer-element' key={this.state.ID} onClick={this.props.onClick}>
<li>{this.state.name}</li>
</div>
<div className='bg-primary text-light p-1' onClick={this.props.onLeave}>Leave</div>
</div>

)
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/server/utilities.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const crypto = require('crypto')
const _ = require('lodash')
const WEBSOCKET_COOKIE_NAME = 'io'
const util = require('util')
class Utilities {

static generateSalt (size) {
Expand Down Expand Up @@ -31,6 +32,20 @@ class Utilities {
io.of(namespaceInfo.name)
.on('connection', bindEventListenersToSocket(namespaceInfo, ControllerClass, connectionsRepository))
}

// This is an utility for debugging websocket room members
static getUsersInWebsocketRoom(socket, roomId) {
const room = socket.nsp.in(roomId)
return getRoomClients(room)
.then(clients => {
return _.map(clients, socketId =>
getUsername(room.connected[socketId]))
})
}
}

function getUsername(socket) {
return socket.request.user.username
}

function bindEventListenersToSocket(namespaceInfo, ControllerClass, connectionsRepository) {
Expand All @@ -50,6 +65,11 @@ function bindEventListenersToSocket(namespaceInfo, ControllerClass, connectionsR
}
}

async function getRoomClients(room) {
const getClients = room.clients.bind(room)
return await util.promisify(getClients)()
}

function runConnectionEventHandler(socketToPass, controller, connectionsRepository) {
controller.connection(socketToPass, connectionsRepository)
}
Expand Down
44 changes: 25 additions & 19 deletions src/server/ws-routes/controllers/Room.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Room {
RoomModel.findOne({id: roomId}, 'id members').exec()
.then(room => {
if (room !== null)
return joinWebsocketConnectionToRoom(socket, room)
return joinWebsocketConnectionToRoom(socket, room.id)
.then(broadcastInformationAboutNewMember.bind(null, socket, room.id))
.then(updateRoomMembersListInMongo.bind(null, room, username))
this.create({invitedUsersIndexes: [], roomId}, socket, connections)
Expand Down Expand Up @@ -78,20 +78,23 @@ class Room {
[EVENT_TYPES.MESSAGE](data, socket) {
const {roomId, message} = data
const username = socket.request.user.username
const response = new MessageResponse(username, roomId, message)
const messageDataToSaveInDb = {
author: username,
text: message,
roomId,
date: response.date
}
const messageModelInstance = new MessageModel(messageDataToSaveInDb)
messageModelInstance.save()
.catch(err => {
logger.error(err)
return RoomModel.findOne({id: roomId}, 'members').exec()
.then(room => {
const isMemberOfRoom = room.members.indexOf(username) !== -1
if (!isMemberOfRoom) return
const response = new MessageResponse(username, roomId, message)
const messageDataToSaveInDb = {
author: username,
text: message,
roomId,
date: response.date
}
const messageModelInstance = new MessageModel(messageDataToSaveInDb)
messageModelInstance.save()
.catch(err => logger.error(err))
socket.emit(EVENT_TYPES.MESSAGE, response.serialize())
socket.to(roomId).emit(EVENT_TYPES.MESSAGE, response.serialize())
})
socket.emit(EVENT_TYPES.MESSAGE, response.serialize())
socket.to(roomId).emit(EVENT_TYPES.MESSAGE, response.serialize())
}

/**
Expand All @@ -106,11 +109,16 @@ class Room {

[EVENT_TYPES.LEAVE](data, socket) {
const {roomId} = data

const username = socket.request.user.username
const response = new LeaveResponse(username, roomId)
socket.emit(EVENT_TYPES.LEAVE, response.serialize())
socket.to(roomId).emit(EVENT_TYPES.LEAVE, response.serialize())
socket.leave({roomId})
socket.leave(roomId)
return RoomModel.findOne({id: roomId}, 'members').exec()
.then(room => {
room.members = room.members.filter(member => member !== username)
return room.save()
})
}

/**
Expand Down Expand Up @@ -187,9 +195,7 @@ function broadcastInformationAboutNewMember(socket, roomId) {
}

function joinWebsocketConnectionToRoom(socket, roomId) {
return new Promise(resolve => {
socket.join(roomId, resolve)
})
return new Promise(resolve => socket.join(roomId, resolve))
}

module.exports = Room

0 comments on commit a2d4313

Please sign in to comment.