feat: usernames

This commit is contained in:
cha0s 2020-07-18 04:29:26 -05:00
parent 3ce283ff4c
commit 7c10bc807c
6 changed files with 82 additions and 13 deletions

View File

@ -4,7 +4,9 @@ import Message from '~/common/packets/message.packet';
import { import {
addMessage, addMessage,
confirmMessage, confirmMessage,
fetchUsernames,
join, join,
joined,
leave, leave,
submitJoin, submitJoin,
submitLeave, submitLeave,
@ -14,6 +16,12 @@ import {
import {socket} from '~/client/hooks/useSocket'; import {socket} from '~/client/hooks/useSocket';
const effects = { const effects = {
[join]: ({dispatch}, {payload: {users}}) => {
dispatch(fetchUsernames(users));
},
[joined]: ({dispatch}, {payload: {id}}) => {
dispatch(fetchUsernames([id]));
},
[submitJoin]: ({dispatch}, {payload}) => { [submitJoin]: ({dispatch}, {payload}) => {
const {channel} = payload; const {channel} = payload;
socket.send(new Join(payload), ({messages, users}) => { socket.send(new Join(payload), ({messages, users}) => {
@ -22,9 +30,7 @@ const effects = {
}, },
[submitLeave]: ({dispatch}, {payload}) => { [submitLeave]: ({dispatch}, {payload}) => {
const {channel} = payload; const {channel} = payload;
socket.send(new Leave(payload), () => { socket.send(new Leave(payload), () => dispatch(leave({channel})));
dispatch(leave({channel}));
});
}, },
[submitMessage]: ({dispatch}, {payload}) => { [submitMessage]: ({dispatch}, {payload}) => {
dispatch(addMessage(payload)); dispatch(addMessage(payload));

View File

@ -0,0 +1,14 @@
import {Packet} from '@avocado/net';
export default class Leave extends Packet {
static get schema() {
return {
...super.schema,
data: [
'uint32',
],
};
}
}

View File

@ -1,9 +1,14 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { import {
createAsyncThunk,
createSelector, createSelector,
createSlice, createSlice,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import Usernames from '~/common/packets/usernames.packet';
import {socket} from '~/client/hooks/useSocket';
import hydration from './hydration'; import hydration from './hydration';
export const channelsSelector = (state) => state.chat.channels; export const channelsSelector = (state) => state.chat.channels;
@ -20,6 +25,20 @@ export const channelMessagesSelector = createSelector(
(channel, messages) => (!channel ? [] : channel.messages.map((uuid) => messages[uuid])), (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({ const slice = createSlice({
name: 'chat', name: 'chat',
initialState: { initialState: {
@ -93,6 +112,13 @@ const slice = createSlice({
submitLeave: () => {}, submitLeave: () => {},
submitMessage: () => {}, submitMessage: () => {},
}, },
extraReducers: {
[fetchUsernames.fulfilled]: ({users}, {payload}) => {
payload.forEach(([id, name]) => {
users[id] = name;
});
},
},
}); });
export const { export const {

View File

@ -3,6 +3,7 @@ import {promisify} from 'util';
import {joinChannel} from '~/common/channel'; import {joinChannel} from '~/common/channel';
import {allModels} from './models/registrar';
import createRedisClient, {keys} from './redis'; import createRedisClient, {keys} from './redis';
const redisClient = createRedisClient(); const redisClient = createRedisClient();
@ -41,23 +42,25 @@ export const channelState = async (req, channel) => {
}; };
}; };
export const userState = async (req) => { const userState = async (user) => (
const {user} = req; user
return user
? { ? {
favorites: [], favorites: [],
friends: await user.friends(), friends: await user.friends(),
id: user.id, id: user.id,
redditUsername: user.redditUsername, redditUsername: user.redditUsername,
} }
: null; : null
}; );
export const appUserState = async (req) => userState(req.user);
export const channelsToHydrate = async (req) => ( export const channelsToHydrate = async (req) => (
(req.channel ? [req.channel] : []).concat(req.user ? await req.user.favorites() : []) (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); const toHydrate = await channelsToHydrate(req);
if (0 === toHydrate.length) { if (0 === toHydrate.length) {
return null; return null;
@ -67,13 +70,16 @@ export const chatState = async (req) => {
focus: req.channel ? joinChannel(req.channel) : '', focus: req.channel ? joinChannel(req.channel) : '',
messages: {}, messages: {},
recent: [], recent: [],
users: {},
}; };
const entries = await Promise.all( const entries = await Promise.all(
toHydrate.map((favorite) => channelState(req, joinChannel(favorite))), toHydrate.map((favorite) => channelState(req, joinChannel(favorite))),
); );
const chatUsers = [];
for (let i = 0; i < toHydrate.length; i++) { for (let i = 0; i < toHydrate.length; i++) {
const channel = joinChannel(toHydrate[i]); const channel = joinChannel(toHydrate[i]);
const {messages, users} = entries[i]; const {messages, users} = entries[i];
chatUsers.push(...users);
chat.channels[channel] = { chat.channels[channel] = {
messages: messages.map((message) => message.uuid), messages: messages.map((message) => message.uuid),
users, users,
@ -83,5 +89,14 @@ export const chatState = async (req) => {
}); });
chat.recent.push(channel); 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; return chat;
}; };

View File

@ -1,7 +1,7 @@
import session from 'express-session'; import session from 'express-session';
import {registerHooks} from 'scwp'; import {registerHooks} from 'scwp';
import {chatState, userState} from './entry'; import {appChatState, appUserState} from './entry';
import createRedisClient from './redis'; import createRedisClient from './redis';
const redisClient = createRedisClient(); const redisClient = createRedisClient();
@ -20,7 +20,7 @@ export default (options = {}) => (
registerHooks({ registerHooks({
hydration: async (req) => ({ hydration: async (req) => ({
chat: await chatState(req), chat: await appChatState(req),
user: await userState(req), user: await appUserState(req),
}), }),
}, module.id); }, module.id);

View File

@ -11,6 +11,7 @@ import {joinChannel, parseChannel} from '~/common/channel';
import Join from '~/common/packets/join.packet'; import Join from '~/common/packets/join.packet';
import Leave from '~/common/packets/leave.packet'; import Leave from '~/common/packets/leave.packet';
import Message from '~/common/packets/message.packet'; import Message from '~/common/packets/message.packet';
import Usernames from '~/common/packets/usernames.packet';
import { import {
channelsToHydrate, channelsToHydrate,
@ -19,8 +20,9 @@ import {
channelUsers, channelUsers,
} from '~/server/entry'; } from '~/server/entry';
import {allModels} from './models/registrar';
import passport from './passport'; import passport from './passport';
import createRedisClient, {keys} from './redis'; import createRedisClient from './redis';
import session from './session'; import session from './session';
const pubClient = createRedisClient(); const pubClient = createRedisClient();
@ -28,6 +30,7 @@ const subClient = createRedisClient();
const adapter = redisAdapter({pubClient, subClient}); const adapter = redisAdapter({pubClient, subClient});
export function createSocketServer(httpServer) { export function createSocketServer(httpServer) {
const {User} = allModels();
const socketServer = new SocketServer(httpServer, { const socketServer = new SocketServer(httpServer, {
adapter, adapter,
}); });
@ -105,6 +108,11 @@ export function createSocketServer(httpServer) {
.expire(key, 600) .expire(key, 600)
.exec(() => fn([timestamp, uuid])); .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 () => { socket.on('disconnecting', async () => {
Object.keys(socket.socket.rooms).forEach((room) => { Object.keys(socket.socket.rooms).forEach((room) => {