diff --git a/server/socket/index.js b/server/socket/index.js index 129bb89..65a1b38 100644 --- a/server/socket/index.js +++ b/server/socket/index.js @@ -14,6 +14,10 @@ module.exports = io => { ) socket.on('message', message => { + if (!message) { + console.log('Weird. Received empty message.') + return + } console.log('sending message to ', message.to, message) io.to(message.to).emit('message', message) }) @@ -21,6 +25,7 @@ module.exports = io => { socket.on('join', room => { console.log(`${socket.id} joins ${room}`) socket.join(room, () => { + sendNamespace(room) io.to(room).emit('user joined', { room, socketId: socket.id }) io.of(room).clients((error, clients) => { if (error) throw error @@ -34,31 +39,48 @@ module.exports = io => { console.log('all clients:', c) }) }) - socket.on('part', namespace => { - console.log('part', socket.id, namespace) - io.to(namespace).emit('user left', { namespace, socketId: socket.id }) - socket.leave(namespace) + socket.on('part', room => { + console.log('part', socket.id, room) + socket.leave(room, () => { + console.log(Object.keys(io.sockets.adapter.rooms)) + if (io.sockets.adapter.rooms[room]) { + Object.keys(io.sockets.adapter.rooms[room]['sockets']).map(s => { + sendNamespace(room, s) + return s + }) + } else { + console.log(`room ${room} disappeared`) + } + io.to(room).emit('user left', { room, socketId: socket.id }) + }) }) socket.on('change nick', nick => { - io.emit('changed nick', { socketId: socket.id, nick: nick }) + console.log(`${users[socket.id]['nick']} changed nick to ${nick}`) + socket.broadcast.emit('changed nick', { socketId: socket.id, nick: nick }) users[socket.id]['nick'] = nick }) - socket.on('get namespaces', () => sendNamespaces()) - - const sendNamespaces = () => { - const namespaces = Object.keys(io.sockets.adapter.rooms).map(k => ({ - namespace: k, - sockets: Object.keys(io.sockets.adapter.rooms[k]['sockets']), - })) - io.emit('got namespaces', namespaces) + const sendNamespace = (room, socketId) => { + if (io.sockets.adapter.rooms[room]) { + const namespace = { + namespace: room, + sockets: Object.keys(io.sockets.adapter.rooms[room]['sockets']), + } + if (socketId) { + socket.emit('got namespace', namespace) + } else { + io.emit('got namespace', namespace) + } + } } socket.on('disconnect', async () => { - io.emit('user disconnected', socket.id) console.log(`${socket.id} has disconnected.`) - //sendNamespaces() + io.emit('user disconnected', socket.id) + // OPTIMIZE if there's an easy way to inform rooms the socket was part of + // they should be informed here + //socket.rooms.map(r => r.sockets.map(s => sendNamespace(r, s))) }) }) } diff --git a/src/App.js b/src/App.js index e542c72..edf24b2 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,11 @@ import moment from 'moment' const initialState = { loading: true, + messages: [], + allNamespaces: [], + chats: [], + room: '/', + allUsers: [], allSockets: [], allMessages: [], buffers: [], @@ -18,9 +23,8 @@ class App extends React.Component { super() this.state = initialState this.socket = socket - this.submitMessage = this.submitMessage.bind(this) this.join = this.join.bind(this) - this.readMessages = this.readMessages.bind(this) + this.formatMessage = this.formatMessage.bind(this) this.countUnreadMessages = this.countUnreadMessages.bind(this) this.readMessages = this.readMessages.bind(this) this.activateChat = this.activateChat.bind(this) @@ -33,6 +37,9 @@ class App extends React.Component { componentDidMount() { this.fetchData() + + // EVENTS + this.socket.on('connect', () => { console.log(`I am ${this.socket.id}`) this.join('#projex') @@ -46,73 +53,64 @@ class App extends React.Component { }) this.socket.on('user disconnected', socketId => { - console.log(`${socketId} disconnected.`) + // mark user as offline + const oldNick = this.state.allUsers.find(u => u.socketId === socketId) + .nick + console.log(`${oldNick} disconnected.`) const allUsers = this.state.allUsers.map(u => { + // TODO find a better way to preserve existing chat windows if (u.socketId === socketId) u.offline = true return u }) - const oldNick = allUsers.find(u => u.socketId === socketId).nick this.setState({ allUsers }) - // inform channels user was part of - this.state.chats.map(c => { - if (c === socketId) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} disconnected`, c) - ) - this.setState({ messages }) - } - this.state.allNamespaces.map(n => { - if (n.namespace === c && n.sockets.find(s => s === socketId)) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} disconnected`, c) - ) - this.setState({ messages }) - } - return n + // inform sockets the user was chatting with + this.state.chats + .filter(c => c === socketId) + .map(c => { + this.addNotification(`${oldNick} disconnected`, this.socket.id, c) + return c }) - return c + // inform channels the user was part of and update their socket list + const allNamespaces = this.state.allNamespaces.map(n => { + if (n.sockets.find(s => s === socketId)) { + n.sockets = n.sockets.filter(s => s === socketId) + this.addNotification(`${oldNick} disconnected`, n.namespace) + } + return n }) + this.setState({ allNamespaces }) }) this.socket.on('user joined', data => { - const { namespace, socketId } = data + const { room, socketId } = data const user = this.state.allUsers.find(u => u.socketId === socketId) - console.log(`${user.nick} joined ${namespace}`) - const messages = this.state.messages.concat( - this.addNotification(`${user.nick} joined ${namespace}`, namespace) - ) - this.setState({ messages }) + console.log(`${user.nick} joined ${room}`) + this.addNotification(`${user.nick} joined ${room}`, room) }) this.socket.on('user left', data => { - const { namespace, socketId } = data + const { room, socketId } = data const user = this.state.allUsers.find(u => u.socketId === socketId) - console.log(`${user.nick} left ${namespace}`) - const messages = this.state.messages.concat( - this.addNotification(`${user.nick} left ${namespace}`, namespace) - ) - this.setState({ messages }) + console.log(`${user.nick} left ${room}`) + this.addNotification(`${user.nick} left ${room}`, room) }) this.socket.on('changed nick', user => { - if (user.socketId === this.state.user.socketId) return console.log(`${user.socketId} is now known as ${user.nick}.`) const allUsers = this.state.allUsers const oldNick = allUsers.find(u => u.socketId === user.socketId).nick // inform channels user is part of this.state.chats.map(c => { if (c === user.socketId) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} is now known as ${user.nick}`, c) + this.addNotification( + `${oldNick} is now known as ${user.nick}`, + this.socket.id, + c ) - this.setState({ messages }) } this.state.allNamespaces.map(n => { if (n.namespace === c && n.sockets.find(s => s === user.socketId)) { - const messages = this.state.messages.concat( - this.addNotification(`${oldNick} is now known as ${user.nick}`, c) - ) - this.setState({ messages }) + this.addNotification(`${oldNick} is now known as ${user.nick}`, c) } return n }) @@ -120,7 +118,7 @@ class App extends React.Component { }) // update nick allUsers.map(u => { - if (u.socketId === user.socketId) u.nick = user.nick + if (u.socketId === user.socketId) u.nick = this.socket.nick return u }) this.setState({ allUsers }) @@ -134,11 +132,6 @@ class App extends React.Component { this.setState({ allNamespaces }) }) - this.socket.on('got namespaces', allNamespaces => { - console.log('got namespaces:', allNamespaces) - this.setState({ allNamespaces }) - }) - this.socket.on('got users', allUsers => { console.log('got users:', allUsers) this.setState({ allUsers }) @@ -146,7 +139,7 @@ class App extends React.Component { this.socket.on('message', msg => { if ( - msg.to === this.state.user.socketId && + msg.to === this.socket.id && !this.state.chats.find(c => c === msg.from) ) { console.log(`${msg.from} wants to chat with me.`) @@ -154,50 +147,65 @@ class App extends React.Component { this.setState({ chats }) } if ( - (msg.to[0] === '#' && msg.to === this.state.namespace) || - (msg.to[0] !== '#' && msg.from === this.state.namespace) + (msg.to[0] === '#' && msg.to === this.state.buffer) || + (msg.to[0] !== '#' && msg.from === this.state.buffer) ) msg.read = true const messages = this.state.messages.concat(msg) - console.log(msg) this.setState({ messages }) }) } - parseCommand() { - const args = this.state.message.split(' ') - const cmd = args[0].slice(1) - if (cmd === 'join') { - if (!args[1]) { - alert('did you forget a channel to join?') - return - } - this.join(args[1]) - } else if (cmd === 'part' || cmd === 'leave') { - this.part(args[1] ? args[1] : this.state.namespace) - } else if (cmd === 'nick') this.nick(args[1]) - else { - alert(`unknown command: ${cmd}`) + // FUNCTIONS + + join(room) { + if (room[0] !== '#') return alert('Use a leading #, silly!') + if (this.state.chats.find(c => c === room)) return + console.log('joining', room) + const chats = this.state.chats.concat(room) + this.socket.emit('join', room, ack => console.log('ack', ack)) + this.activateChat(room, chats) + } + query(user) { + if (this.state.chats.find(c => c === user.socketId)) { + this.activateChat(user.socketId) + return } this.setState({ message: '' }) + console.log(`starting chat with ${user.nick}`) + const chats = this.state.chats.concat(user.socketId) + this.activateChat(user.socketId, chats) } - addNotification(text, namespace) { - return this.formatMessage( - text, - 'server', - 'server', - namespace, - namespace === this.state.namespace ? true : false + addNotification(text, to, from) { + if (!text || !to) { + console.log('[addNotification] Missing parameter(s)', text, to) + return + } + const messages = this.state.messages.concat( + this.formatMessage( + text, + from ? from : 'server', + 'server', + to, + to === this.state.buffer ? true : false + ) ) + this.setState({ messages }) } formatMessage(text, from, nick, to, read) { if (!text || !from || !to || !nick) { console.log( - '[formatMessage] Dropping malformed message (requiring text, from, nick and to).' + '[formatMessage] Dropping malformed message (requiring text, from, nick and to).', + text, + from, + nick, + to, + read ) + return } - const message = { + return { id: uuid(), date: moment().format('MMMM D YYYY, h:mm a'), text: text, @@ -206,22 +214,6 @@ class App extends React.Component { to: to, read: read, } - return message - } - submitMessage(msg) { - console.log(msg) - if (msg[0] === '/') return this.parseCommand() - if (!msg.length > 0) return - - this.sendMessage( - this.formatMessage( - msg, - this.state.user.socketId, - this.state.user.nick, - this.state.namespace - ) - ) - this.setState({ message: '' }) } renderLoading() { return