feat: http join/leave
This commit is contained in:
parent
605150e984
commit
00cf0979bb
|
@ -2,8 +2,10 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {useDispatch} from 'react-redux';
|
import {useDispatch} from 'react-redux';
|
||||||
|
|
||||||
|
import Join from '~/common/packets/join.packet';
|
||||||
|
import Leave from '~/common/packets/leave.packet';
|
||||||
import Message from '~/common/packets/message.packet';
|
import Message from '~/common/packets/message.packet';
|
||||||
import {addMessage} from '~/common/state/chat';
|
import {addMessage, joined, left} from '~/common/state/chat';
|
||||||
|
|
||||||
import useSocket from './hooks/useSocket';
|
import useSocket from './hooks/useSocket';
|
||||||
|
|
||||||
|
@ -14,6 +16,12 @@ export default function Dispatcher({children}) {
|
||||||
if (packet instanceof Message) {
|
if (packet instanceof Message) {
|
||||||
dispatch(addMessage(packet.data));
|
dispatch(addMessage(packet.data));
|
||||||
}
|
}
|
||||||
|
if (packet instanceof Join) {
|
||||||
|
dispatch(joined(packet.data));
|
||||||
|
}
|
||||||
|
if (packet instanceof Leave) {
|
||||||
|
dispatch(left(packet.data));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
socket.on('packet', onPacket);
|
socket.on('packet', onPacket);
|
||||||
socket.on('disconnect', () => socket.off('packet', onPacket));
|
socket.on('disconnect', () => socket.off('packet', onPacket));
|
||||||
|
|
|
@ -5,7 +5,10 @@ export default class Join extends Packet {
|
||||||
static get schema() {
|
static get schema() {
|
||||||
return {
|
return {
|
||||||
...super.schema,
|
...super.schema,
|
||||||
data: 'string',
|
data: {
|
||||||
|
id: 'uint32',
|
||||||
|
channel: 'string',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,10 @@ export default class Leave extends Packet {
|
||||||
static get schema() {
|
static get schema() {
|
||||||
return {
|
return {
|
||||||
...super.schema,
|
...super.schema,
|
||||||
data: 'string',
|
data: {
|
||||||
|
id: 'uint32',
|
||||||
|
channel: 'string',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,9 +61,8 @@ const slice = createSlice({
|
||||||
focus: ({unread}, {payload: {channel}}) => {
|
focus: ({unread}, {payload: {channel}}) => {
|
||||||
unread[channel] = 0;
|
unread[channel] = 0;
|
||||||
},
|
},
|
||||||
join: ({channels, unread}, {payload: {channel, messages, users}}) => {
|
join: ({channels}, {payload: {channel, messages, users}}) => {
|
||||||
channels[channel] = {messages, users};
|
channels[channel] = {messages, users};
|
||||||
unread[channel] = 0;
|
|
||||||
},
|
},
|
||||||
joined: ({channels}, {payload: {channel, id}}) => {
|
joined: ({channels}, {payload: {channel, id}}) => {
|
||||||
channels[channel].users.push(id);
|
channels[channel].users.push(id);
|
||||||
|
|
|
@ -8,14 +8,17 @@ import createRedisClient, {keys} from './redis';
|
||||||
const redisClient = createRedisClient();
|
const redisClient = createRedisClient();
|
||||||
const mget = promisify(redisClient.mget.bind(redisClient));
|
const mget = promisify(redisClient.mget.bind(redisClient));
|
||||||
|
|
||||||
export const channelUsers = async (channel) => {
|
export const channelUserCounts = async (channel) => {
|
||||||
const socketKeys = await keys(redisClient, `${channel}:users:*`);
|
const socketKeys = await keys(redisClient, `${channel}:users:*`);
|
||||||
return 0 === socketKeys.length
|
return 0 === socketKeys.length
|
||||||
? []
|
? []
|
||||||
: Object.keys((await mget(socketKeys)).reduce((r, k) => ({[k]: true, ...r}), {}))
|
: (await mget(...socketKeys)).reduce((r, k) => ({...r, [k]: 1 + (r[k] || 0)}), {});
|
||||||
.map((idStrings) => parseInt(idStrings, 10));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const channelUsers = async (channel) => (
|
||||||
|
Object.keys(await channelUserCounts(channel)).map((id) => parseInt(id, 10))
|
||||||
|
);
|
||||||
|
|
||||||
const channelState = async (req, channel) => {
|
const channelState = async (req, channel) => {
|
||||||
const messageKeys = await keys(redisClient, `${channel}:messages:*`);
|
const messageKeys = await keys(redisClient, `${channel}:messages:*`);
|
||||||
const messages = 0 === messageKeys.length
|
const messages = 0 === messageKeys.length
|
||||||
|
@ -26,9 +29,11 @@ const channelState = async (req, channel) => {
|
||||||
uuid: messageKeys[i].split(':')[2],
|
uuid: messageKeys[i].split(':')[2],
|
||||||
}))
|
}))
|
||||||
.sort((l, r) => l.timestamp - r.timestamp);
|
.sort((l, r) => l.timestamp - r.timestamp);
|
||||||
|
const userId = req.user ? req.user.id : 0;
|
||||||
|
const users = await channelUsers(channel);
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
users: channelUsers(channel),
|
users: -1 !== users.indexOf(userId) ? users : users.concat([userId]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,11 @@ import {SocketServer} from '@avocado/net/server/socket';
|
||||||
import socketSession from 'express-socket.io-session';
|
import socketSession from 'express-socket.io-session';
|
||||||
|
|
||||||
import {joinChannel, parseChannel} from '~/common/channel';
|
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 Message from '~/common/packets/message.packet';
|
||||||
|
|
||||||
import {channelsToHydrate} from '~/server/entry';
|
import {channelsToHydrate, channelUserCounts, channelUsers} from '~/server/entry';
|
||||||
|
|
||||||
import passport from './passport';
|
import passport from './passport';
|
||||||
import createRedisClient, {keys} from './redis';
|
import createRedisClient, {keys} from './redis';
|
||||||
|
@ -19,6 +21,7 @@ import session from './session';
|
||||||
const pubClient = createRedisClient();
|
const pubClient = createRedisClient();
|
||||||
const subClient = createRedisClient();
|
const subClient = createRedisClient();
|
||||||
const adapter = redisAdapter({pubClient, subClient});
|
const adapter = redisAdapter({pubClient, subClient});
|
||||||
|
const del = promisify(pubClient.del.bind(pubClient));
|
||||||
const set = promisify(pubClient.set.bind(pubClient));
|
const set = promisify(pubClient.set.bind(pubClient));
|
||||||
|
|
||||||
export function createSocketServer(httpServer) {
|
export function createSocketServer(httpServer) {
|
||||||
|
@ -38,14 +41,19 @@ export function createSocketServer(httpServer) {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
socketServer.io.use(async (socket, next) => {
|
socketServer.io.use(async (socket, next) => {
|
||||||
|
const {user} = socket.handshake;
|
||||||
const join = promisify(socket.join.bind(socket));
|
const join = promisify(socket.join.bind(socket));
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
channelsToHydrate(socket.handshake)
|
channelsToHydrate(socket.handshake)
|
||||||
|
.map((channel) => joinChannel(channel))
|
||||||
.map(async (channel) => {
|
.map(async (channel) => {
|
||||||
const channelString = joinChannel(channel);
|
await join(channel);
|
||||||
await join(channelString);
|
const users = await channelUsers(channel);
|
||||||
const {user} = socket.handshake;
|
const id = user ? user.id : 0;
|
||||||
await set(`${channelString}:users:${socket.id}`, user ? user.id : 0);
|
if (-1 === users.indexOf(id)) {
|
||||||
|
socketServer.send(new Join({channel, id}), channel);
|
||||||
|
}
|
||||||
|
await set(`${channel}:users:${socket.id}`, user ? user.id : 0);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
next();
|
next();
|
||||||
|
@ -78,8 +86,17 @@ export function createSocketServer(httpServer) {
|
||||||
});
|
});
|
||||||
socket.on('disconnect', async () => {
|
socket.on('disconnect', async () => {
|
||||||
const socketKeys = await keys(pubClient, `*:users:${socket.id}`);
|
const socketKeys = await keys(pubClient, `*:users:${socket.id}`);
|
||||||
|
const {user} = req;
|
||||||
if (socketKeys.length > 0) {
|
if (socketKeys.length > 0) {
|
||||||
pubClient.del(socketKeys);
|
const channels = socketKeys.map((key) => key.split(':')[0]);
|
||||||
|
await Promise.all(channels.map(async (channel) => {
|
||||||
|
const userCounts = await channelUserCounts(channel);
|
||||||
|
const id = user ? user.id : 0;
|
||||||
|
if (1 === userCounts[id]) {
|
||||||
|
socketServer.send(new Leave({channel, id}), channel);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
await del(socketKeys);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user