diff --git a/src/client/chat--message.jsx b/src/client/chat--message.jsx index dfa39f2..c9528ad 100644 --- a/src/client/chat--message.jsx +++ b/src/client/chat--message.jsx @@ -4,13 +4,13 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import ReactMarkdown from 'react-markdown'; +import {useSelector} from 'react-redux'; + +import {usernameSelector} from '~/common/state/usernames'; export default function ChatMessage(props) { const {message: {owner, message, timestamp}, isShort} = props; - const ownerMap = { - 1: 'cha0s', - 2: 'BabeHasHiccups', - }; + const username = useSelector((state) => usernameSelector(state, owner)); const dtf = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', @@ -31,7 +31,7 @@ export default function ChatMessage(props) { { !isShort && (
-
{ownerMap[owner]}
+
{username}
{$messageTime}
) diff --git a/src/client/store/effects.js b/src/client/store/effects.js index 0027819..49412aa 100644 --- a/src/client/store/effects.js +++ b/src/client/store/effects.js @@ -4,7 +4,6 @@ import Message from '~/common/packets/message.packet'; import { addMessage, confirmMessage, - fetchUsernames, join, joined, leave, @@ -13,6 +12,10 @@ import { submitMessage, } from '~/common/state/chat'; +import { + fetchUsernames, +} from '~/common/state/usernames'; + import {socket} from '~/client/hooks/useSocket'; const effects = { diff --git a/src/client/store/index.js b/src/client/store/index.js index 3c5d9f2..923fdc4 100644 --- a/src/client/store/index.js +++ b/src/client/store/index.js @@ -3,6 +3,7 @@ import {combineReducers} from 'redux'; import chat from '~/common/state/chat'; import user from '~/common/state/user'; +import usernames from '~/common/state/usernames'; import createCommonStore from '~/common/store'; import {middleware as effectsMiddleware} from './effects'; @@ -10,6 +11,7 @@ import {middleware as effectsMiddleware} from './effects'; const reducer = combineReducers({ chat, user, + usernames, }); export default function createStore(options = {}) { diff --git a/src/common/packets/usernames.packet.js b/src/common/packets/usernames.packet.js index d78c666..a63823b 100644 --- a/src/common/packets/usernames.packet.js +++ b/src/common/packets/usernames.packet.js @@ -1,6 +1,6 @@ import {Packet} from '@avocado/net'; -export default class Leave extends Packet { +export default class Usernames extends Packet { static get schema() { return { diff --git a/src/common/state/chat.js b/src/common/state/chat.js index b4605e8..e22288f 100644 --- a/src/common/state/chat.js +++ b/src/common/state/chat.js @@ -1,14 +1,9 @@ /* 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; @@ -25,20 +20,6 @@ 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: { @@ -47,7 +28,6 @@ const slice = createSlice({ messages: {}, recent: [], unread: {}, - users: {}, ...(hydration('chat') || {}), }, reducers: { @@ -112,13 +92,6 @@ const slice = createSlice({ submitLeave: () => {}, submitMessage: () => {}, }, - extraReducers: { - [fetchUsernames.fulfilled]: ({users}, {payload}) => { - payload.forEach(([id, name]) => { - users[id] = name; - }); - }, - }, }); export const { diff --git a/src/common/state/usernames.js b/src/common/state/usernames.js new file mode 100644 index 0000000..2271f0a --- /dev/null +++ b/src/common/state/usernames.js @@ -0,0 +1,56 @@ +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 usernamesSelector = (state) => state.usernames; + +export const usernameSelector = createSelector( + [usernamesSelector, (_, id) => id], + (usernames, id) => usernames[id], +); + +export const fetchUsernames = createAsyncThunk( + 'usernames/fetchUsernames', + async (ids, {getState}) => { + const usernames = usernamesSelector(getState()); + const missingIds = ids.filter((id) => !usernames[id]); + if (0 === missingIds.length) { + return []; + } + return new Promise((resolve) => ( + socket.send(new Usernames(missingIds), (usernames) => { + resolve(missingIds.map((id, i) => [id, usernames[i]])); + }) + )); + }, +); + +/* eslint-disable no-param-reassign */ +const slice = createSlice({ + name: 'usernames', + initialState: hydration('usernames') || {}, + extraReducers: { + [fetchUsernames.fulfilled]: (state, {payload}) => { + payload.forEach(([id, name]) => { + state[id] = name; + }); + }, + }, +}); +/* eslint-enable no-param-reassign */ + +export const { + addFriend, + blockUser, + removeFriend, +} = slice.actions; + +export default slice.reducer; diff --git a/src/server/entry.js b/src/server/entry.js index 0c0df7f..e5149e8 100644 --- a/src/server/entry.js +++ b/src/server/entry.js @@ -59,12 +59,29 @@ export const channelsToHydrate = async (req) => ( (req.channel ? [req.channel] : []).concat(req.user ? await req.user.favorites() : []) ); +const chatUsers = async (req) => { + const toHydrate = await channelsToHydrate(req); + if (0 === toHydrate.length) { + return []; + } + const entries = await Promise.all( + toHydrate.map((favorite) => channelState(req, joinChannel(favorite))), + ); + const chatUsers = []; + for (let i = 0; i < toHydrate.length; i++) { + chatUsers.push(...entries[i].users); + } + return chatUsers; +}; + export const appChatState = async (req) => { - const {User} = allModels(); const toHydrate = await channelsToHydrate(req); if (0 === toHydrate.length) { return null; } + const entries = await Promise.all( + toHydrate.map((favorite) => channelState(req, joinChannel(favorite))), + ); const chat = { channels: {}, focus: req.channel ? joinChannel(req.channel) : '', @@ -72,14 +89,9 @@ export const appChatState = async (req) => { 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, @@ -89,14 +101,14 @@ export const appChatState = 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; }; + +export const appUsernamesState = async (req) => { + const {User} = allModels(); + const usernames = await Promise.all( + (await chatUsers(req)) + .map(async (id) => [id, (await User.findByPk(id)).redditUsername]), + ); + return usernames.reduce((r, [id, username]) => ({...r, [id]: username}), {}); +}; diff --git a/src/server/session.js b/src/server/session.js index 497b7fb..6cf87c5 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 {appChatState, appUserState} from './entry'; +import {appChatState, appUserState, appUsernamesState} from './entry'; import createRedisClient from './redis'; const redisClient = createRedisClient(); @@ -22,5 +22,6 @@ registerHooks({ hydration: async (req) => ({ chat: await appChatState(req), user: await appUserState(req), + usernames: await appUsernamesState(req), }), }, module.id);