feat: socket room joining

This commit is contained in:
cha0s 2020-07-16 20:37:36 -05:00
parent efd6c3d360
commit 622ddb3f59
8 changed files with 97 additions and 63 deletions

View File

@ -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

10
src/common/channel.js Normal file
View File

@ -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}`;

View File

@ -5,7 +5,7 @@ export default class Join extends Packet {
static get schema() {
return {
...super.schema,
data: {},
data: 'string',
};
}

View File

@ -5,7 +5,7 @@ export default class Leave extends Packet {
static get schema() {
return {
...super.schema,
data: {},
data: 'string',
};
}

View File

@ -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;
};

View File

@ -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');

View File

@ -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);

View File

@ -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()