From 622ddb3f591b52ca17052a3cc193c11329a323ef Mon Sep 17 00:00:00 2001 From: cha0s Date: Thu, 16 Jul 2020 20:37:36 -0500 Subject: [PATCH] feat: socket room joining --- src/client/hooks/useSocket.js | 7 +++- src/common/channel.js | 10 ++++++ src/common/packets/join.packet.js | 2 +- src/common/packets/leave.packet.js | 2 +- src/server/entry.js | 58 ++++++++++++++++++++++++------ src/server/http.js | 8 +++-- src/server/session.js | 51 +++----------------------- src/server/sockets.js | 22 +++++++++++- 8 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 src/common/channel.js diff --git a/src/client/hooks/useSocket.js b/src/client/hooks/useSocket.js index 99e5108..9addcd6 100644 --- a/src/client/hooks/useSocket.js +++ b/src/client/hooks/useSocket.js @@ -3,7 +3,12 @@ import {useEffect} from 'react'; import {SocketClient} from '@avocado/net/client/socket'; const isSecure = 'https:' === window.location.protocol; -export const socket = new SocketClient(window.location.host, {secure: isSecure}); +export const socket = new SocketClient(window.location.host, { + query: { + referrer: window.location.pathname, + }, + secure: isSecure, +}); export default function useSocket(fn) { // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/common/channel.js b/src/common/channel.js new file mode 100644 index 0000000..0a19b7c --- /dev/null +++ b/src/common/channel.js @@ -0,0 +1,10 @@ +export const parseChannel = (url) => { + const matches = url.match(/^\/chat\/([^/]+)\/([^/]+)/i); + if (matches) { + const [, type, name] = matches; + return {name, type}; + } + return undefined; +}; + +export const joinChannel = ({name, type}) => `/${type}/${name}`; diff --git a/src/common/packets/join.packet.js b/src/common/packets/join.packet.js index 76bf80c..ba62595 100644 --- a/src/common/packets/join.packet.js +++ b/src/common/packets/join.packet.js @@ -5,7 +5,7 @@ export default class Join extends Packet { static get schema() { return { ...super.schema, - data: {}, + data: 'string', }; } diff --git a/src/common/packets/leave.packet.js b/src/common/packets/leave.packet.js index 6e250fc..d1370d2 100644 --- a/src/common/packets/leave.packet.js +++ b/src/common/packets/leave.packet.js @@ -5,7 +5,7 @@ export default class Leave extends Packet { static get schema() { return { ...super.schema, - data: {}, + data: 'string', }; } diff --git a/src/server/entry.js b/src/server/entry.js index 540a01f..c754013 100644 --- a/src/server/entry.js +++ b/src/server/entry.js @@ -1,18 +1,10 @@ +import {joinChannel} from '~/common/channel'; + import createRedisClient from './redis'; const redisClient = createRedisClient(); -export const channelMiddleware = (req, res, next) => { - const matches = req.url.match(/^\/chat\/([^/]+)\/([^/]+)/i); - if (matches) { - const [, type, name] = matches; - req.channel = {name, type}; - } - next(); -}; - -// eslint-disable-next-line import/prefer-default-export -export const enterChannel = async (req, channel) => { +const enterChannel = async (req, channel) => { const messages = await new Promise((resolve, reject) => { redisClient.scan( 0, @@ -52,3 +44,47 @@ export const enterChannel = async (req, channel) => { messages, }; }; + +export const userState = async (req) => { + const {user} = req; + return user + ? { + favorites: [], + friends: await user.friends(), + redditUsername: user.redditUsername, + } + : null; +}; + +export const channelsToHydrate = (req) => ( + (req.channel ? [req.channel] : []).concat(req.user ? req.user.favorites : []) +); + +export const chatState = async (req) => { + const toHydrate = channelsToHydrate(req); + if (0 === toHydrate.length) { + return null; + } + const chat = { + channels: {}, + focus: req.channel ? joinChannel(req.channel) : '', + messages: {}, + recent: [], + }; + const entries = await Promise.all( + toHydrate.map((favorite) => enterChannel(req, joinChannel(favorite))), + ); + for (let i = 0; i < toHydrate.length; i++) { + const channel = joinChannel(toHydrate[i]); + const {messages} = entries[i]; + chat.channels[channel] = { + messages: messages.map((message) => message.uuid), + users: [], + }; + messages.forEach((message) => { + chat.messages[message.uuid] = message; + }); + chat.recent.push(channel); + } + return chat; +}; diff --git a/src/server/http.js b/src/server/http.js index ef2d0fd..74d6f6c 100644 --- a/src/server/http.js +++ b/src/server/http.js @@ -8,8 +8,9 @@ import express from 'express'; import httpProxy from 'http-proxy'; import {invokeHookFlat} from 'scwp'; +import {parseChannel} from '~/common/channel'; + import userRoutes from './routes/user'; -import {channelMiddleware} from './entry'; import passport from './passport'; import session from './session'; @@ -32,7 +33,10 @@ export async function createHttpServer() { app.use(session()); app.use(passport.initialize()); app.use(passport.session()); - app.use(channelMiddleware); + app.use((req, res, next) => { + req.channel = parseChannel(req.url); + next(); + }); userRoutes(app); const httpServer = http.createServer(app); httpServer.listen(31344, '0.0.0.0'); diff --git a/src/server/session.js b/src/server/session.js index f2d7c2b..bc3cc3d 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 {enterChannel} from './entry'; +import {chatState, userState} from './entry'; import createRedisClient from './redis'; const redisClient = createRedisClient(); @@ -18,50 +18,9 @@ export default (options = {}) => ( }) ); -const channelName = ({name, type}) => `/${type}/${name}`; - registerHooks({ - hydration: async (req) => { - const hydration = {}; - const {user} = req; - const inferred = req.channel; - if (user) { - hydration.user = { - friends: await user.friends(), - redditUsername: user.redditUsername, - }; - } - else { - hydration.user = null; - } - const favorites = []; - if (inferred) { - favorites.push(inferred); - } - if (favorites.length > 0) { - const chat = { - channels: {}, - focus: inferred ? channelName(inferred) : '', - messages: {}, - recent: [], - }; - const entries = await Promise.all( - favorites.map((favorite) => enterChannel(req, channelName(favorite))), - ); - for (let i = 0; i < favorites.length; i++) { - const channel = channelName(favorites[i]); - const {messages} = entries[i]; - chat.channels[channel] = { - messages: messages.map((message) => message.uuid), - users: [], - }; - messages.forEach((message) => { - chat.messages[message.uuid] = message; - }); - chat.recent.push(channel); - } - hydration.chat = chat; - } - return hydration; - }, + hydration: async (req) => ({ + chat: await chatState(req), + user: await userState(req), + }), }, module.id); diff --git a/src/server/sockets.js b/src/server/sockets.js index 5a4ac6d..e435741 100644 --- a/src/server/sockets.js +++ b/src/server/sockets.js @@ -5,8 +5,11 @@ import {v4 as uuidv4} from 'uuid'; import {SocketServer} from '@avocado/net/server/socket'; import socketSession from 'express-socket.io-session'; +import {joinChannel, parseChannel} from '~/common/channel'; import Message from '~/common/packets/message.packet'; +import {channelsToHydrate} from '~/server/entry'; + import passport from './passport'; import createRedisClient from './redis'; import session from './session'; @@ -26,14 +29,31 @@ export function createSocketServer(httpServer) { socketServer.io.use((socket, next) => { passport.session()(socket.handshake, undefined, next); }); + socketServer.io.use((socket, next) => { + // eslint-disable-next-line no-param-reassign + socket.handshake.channel = parseChannel(socket.handshake.query.referrer); + next(); + }); + socketServer.io.use(async (socket, next) => { + await Promise.all( + channelsToHydrate(socket.handshake) + .map((channel) => new Promise((resolve, reject) => { + socket.join( + joinChannel(channel), + (error) => (error ? reject(error) : resolve()), + ); + })), + ); + next(); + }); socketServer.on('connect', (socket) => { const {req} = socket; socket.on('packet', (packet, fn) => { if (packet instanceof Message) { + const {channel, message} = packet.data; const owner = req.user ? req.user.id : 0; const timestamp = Date.now(); const uuid = uuidv4(); - const {channel, message} = packet.data; const key = `${channel}:${uuid}`; pubClient .multi()