From 7c10bc807c1bf0b7d051fca37f0a4e556b2f4abc Mon Sep 17 00:00:00 2001 From: cha0s Date: Sat, 18 Jul 2020 04:29:26 -0500 Subject: [PATCH] feat: usernames --- src/client/store/effects.js | 12 +++++++++--- src/common/packets/usernames.packet.js | 14 +++++++++++++ src/common/state/chat.js | 26 +++++++++++++++++++++++++ src/server/entry.js | 27 ++++++++++++++++++++------ src/server/session.js | 6 +++--- src/server/sockets.js | 10 +++++++++- 6 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 src/common/packets/usernames.packet.js diff --git a/src/client/store/effects.js b/src/client/store/effects.js index 9b9ffd6..0027819 100644 --- a/src/client/store/effects.js +++ b/src/client/store/effects.js @@ -4,7 +4,9 @@ import Message from '~/common/packets/message.packet'; import { addMessage, confirmMessage, + fetchUsernames, join, + joined, leave, submitJoin, submitLeave, @@ -14,6 +16,12 @@ import { import {socket} from '~/client/hooks/useSocket'; const effects = { + [join]: ({dispatch}, {payload: {users}}) => { + dispatch(fetchUsernames(users)); + }, + [joined]: ({dispatch}, {payload: {id}}) => { + dispatch(fetchUsernames([id])); + }, [submitJoin]: ({dispatch}, {payload}) => { const {channel} = payload; socket.send(new Join(payload), ({messages, users}) => { @@ -22,9 +30,7 @@ const effects = { }, [submitLeave]: ({dispatch}, {payload}) => { const {channel} = payload; - socket.send(new Leave(payload), () => { - dispatch(leave({channel})); - }); + socket.send(new Leave(payload), () => dispatch(leave({channel}))); }, [submitMessage]: ({dispatch}, {payload}) => { dispatch(addMessage(payload)); diff --git a/src/common/packets/usernames.packet.js b/src/common/packets/usernames.packet.js new file mode 100644 index 0000000..d78c666 --- /dev/null +++ b/src/common/packets/usernames.packet.js @@ -0,0 +1,14 @@ +import {Packet} from '@avocado/net'; + +export default class Leave extends Packet { + + static get schema() { + return { + ...super.schema, + data: [ + 'uint32', + ], + }; + } + +} diff --git a/src/common/state/chat.js b/src/common/state/chat.js index a5a33b1..b4605e8 100644 --- a/src/common/state/chat.js +++ b/src/common/state/chat.js @@ -1,9 +1,14 @@ /* eslint-disable no-param-reassign */ import { + createAsyncThunk, createSelector, createSlice, } from '@reduxjs/toolkit'; +import Usernames from '~/common/packets/usernames.packet'; + +import {socket} from '~/client/hooks/useSocket'; + import hydration from './hydration'; export const channelsSelector = (state) => state.chat.channels; @@ -20,6 +25,20 @@ export const channelMessagesSelector = createSelector( (channel, messages) => (!channel ? [] : channel.messages.map((uuid) => messages[uuid])), ); +export const fetchUsernames = createAsyncThunk( + 'chat/fetchUsernames', + async (ids, {getState}) => { + const {chat: {users}} = getState(); + const missingIds = ids.filter((id) => !users[id]); + if (0 === missingIds.length) { + return []; + } + return new Promise((resolve) => socket.send(new Usernames(missingIds), (names) => { + resolve(missingIds.map((id, i) => [id, names[i]])); + })); + }, +); + const slice = createSlice({ name: 'chat', initialState: { @@ -93,6 +112,13 @@ const slice = createSlice({ submitLeave: () => {}, submitMessage: () => {}, }, + extraReducers: { + [fetchUsernames.fulfilled]: ({users}, {payload}) => { + payload.forEach(([id, name]) => { + users[id] = name; + }); + }, + }, }); export const { diff --git a/src/server/entry.js b/src/server/entry.js index 4a86670..0c0df7f 100644 --- a/src/server/entry.js +++ b/src/server/entry.js @@ -3,6 +3,7 @@ import {promisify} from 'util'; import {joinChannel} from '~/common/channel'; +import {allModels} from './models/registrar'; import createRedisClient, {keys} from './redis'; const redisClient = createRedisClient(); @@ -41,23 +42,25 @@ export const channelState = async (req, channel) => { }; }; -export const userState = async (req) => { - const {user} = req; - return user +const userState = async (user) => ( + user ? { favorites: [], friends: await user.friends(), id: user.id, redditUsername: user.redditUsername, } - : null; -}; + : null +); + +export const appUserState = async (req) => userState(req.user); export const channelsToHydrate = async (req) => ( (req.channel ? [req.channel] : []).concat(req.user ? await req.user.favorites() : []) ); -export const chatState = async (req) => { +export const appChatState = async (req) => { + const {User} = allModels(); const toHydrate = await channelsToHydrate(req); if (0 === toHydrate.length) { return null; @@ -67,13 +70,16 @@ export const chatState = async (req) => { focus: req.channel ? joinChannel(req.channel) : '', messages: {}, recent: [], + users: {}, }; const entries = await Promise.all( toHydrate.map((favorite) => channelState(req, joinChannel(favorite))), ); + const chatUsers = []; for (let i = 0; i < toHydrate.length; i++) { const channel = joinChannel(toHydrate[i]); const {messages, users} = entries[i]; + chatUsers.push(...users); chat.channels[channel] = { messages: messages.map((message) => message.uuid), users, @@ -83,5 +89,14 @@ export const chatState = async (req) => { }); chat.recent.push(channel); } + const userStates = await Promise.all( + chatUsers.map( + async (id) => userState(await User.findByPk(id)), + ), + ); + chat.users = userStates.reduce( + (r, userState) => ({...r, [userState.id]: userState.redditUsername}), + {}, + ); return chat; }; diff --git a/src/server/session.js b/src/server/session.js index bc3cc3d..497b7fb 100644 --- a/src/server/session.js +++ b/src/server/session.js @@ -1,7 +1,7 @@ import session from 'express-session'; import {registerHooks} from 'scwp'; -import {chatState, userState} from './entry'; +import {appChatState, appUserState} from './entry'; import createRedisClient from './redis'; const redisClient = createRedisClient(); @@ -20,7 +20,7 @@ export default (options = {}) => ( registerHooks({ hydration: async (req) => ({ - chat: await chatState(req), - user: await userState(req), + chat: await appChatState(req), + user: await appUserState(req), }), }, module.id); diff --git a/src/server/sockets.js b/src/server/sockets.js index 2ce6b5d..4eda28a 100644 --- a/src/server/sockets.js +++ b/src/server/sockets.js @@ -11,6 +11,7 @@ import {joinChannel, parseChannel} from '~/common/channel'; import Join from '~/common/packets/join.packet'; import Leave from '~/common/packets/leave.packet'; import Message from '~/common/packets/message.packet'; +import Usernames from '~/common/packets/usernames.packet'; import { channelsToHydrate, @@ -19,8 +20,9 @@ import { channelUsers, } from '~/server/entry'; +import {allModels} from './models/registrar'; import passport from './passport'; -import createRedisClient, {keys} from './redis'; +import createRedisClient from './redis'; import session from './session'; const pubClient = createRedisClient(); @@ -28,6 +30,7 @@ const subClient = createRedisClient(); const adapter = redisAdapter({pubClient, subClient}); export function createSocketServer(httpServer) { + const {User} = allModels(); const socketServer = new SocketServer(httpServer, { adapter, }); @@ -105,6 +108,11 @@ export function createSocketServer(httpServer) { .expire(key, 600) .exec(() => fn([timestamp, uuid])); } + if (packet instanceof Usernames) { + fn(await Promise.all(packet.data.map( + async (id) => (await User.findByPk(id)).redditUsername, + ))); + } }); socket.on('disconnecting', async () => { Object.keys(socket.socket.rooms).forEach((room) => {