feat: literal actual chat
This commit is contained in:
parent
2000f99fb3
commit
ad915391b7
|
@ -56,7 +56,8 @@
|
||||||
"scwp": "1.x",
|
"scwp": "1.x",
|
||||||
"sequelize": "^6.2.4",
|
"sequelize": "^6.2.4",
|
||||||
"socket.io-redis": "^5.3.0",
|
"socket.io-redis": "^5.3.0",
|
||||||
"source-map-support": "^0.5.11"
|
"source-map-support": "^0.5.11",
|
||||||
|
"uuid": "8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutrinojs/airbnb": "^9.1.0",
|
"@neutrinojs/airbnb": "^9.1.0",
|
||||||
|
|
|
@ -6,7 +6,7 @@ import React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
export default function PlayersChatMessageSpace(props) {
|
export default function PlayersChatMessageSpace(props) {
|
||||||
const {message: {owner, text, timestamp}, isShort} = props;
|
const {message: {owner, message, timestamp}, isShort} = props;
|
||||||
const ownerMap = {
|
const ownerMap = {
|
||||||
1: 'cha0s',
|
1: 'cha0s',
|
||||||
2: 'BabeHasHiccups',
|
2: 'BabeHasHiccups',
|
||||||
|
@ -38,7 +38,7 @@ export default function PlayersChatMessageSpace(props) {
|
||||||
}
|
}
|
||||||
<div className="chat--messageText">
|
<div className="chat--messageText">
|
||||||
<div className="chat--messageMarkdown">
|
<div className="chat--messageMarkdown">
|
||||||
<ReactMarkdown source={text} />
|
<ReactMarkdown source={message} />
|
||||||
</div>
|
</div>
|
||||||
{isShort && $messageTime}
|
{isShort && $messageTime}
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@ export default function PlayersChatMessageSpace(props) {
|
||||||
PlayersChatMessageSpace.propTypes = {
|
PlayersChatMessageSpace.propTypes = {
|
||||||
isShort: PropTypes.bool.isRequired,
|
isShort: PropTypes.bool.isRequired,
|
||||||
message: PropTypes.shape({
|
message: PropTypes.shape({
|
||||||
text: PropTypes.string,
|
message: PropTypes.string,
|
||||||
timestamp: PropTypes.number,
|
timestamp: PropTypes.number,
|
||||||
owner: PropTypes.string,
|
owner: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
|
@ -63,12 +63,6 @@ header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat--messageMarkdown {
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
line-height: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat--messageMarkdown {
|
.chat--messageMarkdown {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,18 @@
|
||||||
import './chat--messages.scss';
|
import './chat--messages.scss';
|
||||||
|
|
||||||
import React, {useLayoutEffect, useRef, useState} from 'react';
|
import React, {useLayoutEffect, useRef} from 'react';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import {channelMessagesSelector} from '~/common/state/chat';
|
||||||
|
|
||||||
|
import useChannel from '~/client/hooks/useChannel';
|
||||||
|
|
||||||
import ChatMessage from './chat--message';
|
import ChatMessage from './chat--message';
|
||||||
|
import ChatSubmitMessage from './chat--submitMessage';
|
||||||
|
|
||||||
export default function PlayersChatSpace() {
|
export default function ChatMessages() {
|
||||||
const [$form, $messages] = [useRef(null), useRef(null)];
|
const channel = useChannel();
|
||||||
const [text, setText] = useState('');
|
const $messages = useRef(null);
|
||||||
const messages = [
|
|
||||||
{
|
|
||||||
key: 1,
|
|
||||||
owner: 1,
|
|
||||||
text: 'Hi!',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 2,
|
|
||||||
owner: 2,
|
|
||||||
text: 'Yo.',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 3,
|
|
||||||
owner: 2,
|
|
||||||
text: 'How have you been?',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 4,
|
|
||||||
owner: 1,
|
|
||||||
text: 'Not too bad.',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const {current} = $messages;
|
const {current} = $messages;
|
||||||
const isAtTheBottom = !current
|
const isAtTheBottom = !current
|
||||||
? true
|
? true
|
||||||
|
@ -44,6 +24,7 @@ export default function PlayersChatSpace() {
|
||||||
), current.offsetHeight)
|
), current.offsetHeight)
|
||||||
);
|
);
|
||||||
const heightWatch = current && current.scrollHeight;
|
const heightWatch = current && current.scrollHeight;
|
||||||
|
const messages = useSelector((state) => channelMessagesSelector(state, channel));
|
||||||
const messageCount = messages && messages.length;
|
const messageCount = messages && messages.length;
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (isAtTheBottom) {
|
if (isAtTheBottom) {
|
||||||
|
@ -61,8 +42,8 @@ export default function PlayersChatSpace() {
|
||||||
{messages && messages.map((message) => {
|
{messages && messages.map((message) => {
|
||||||
const $message = (
|
const $message = (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={message.key}
|
key={message.uuid}
|
||||||
isShort={messageOwner === message.owner}
|
isShort={0 === message.owner || messageOwner === message.owner}
|
||||||
message={message}
|
message={message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -70,34 +51,7 @@ export default function PlayersChatSpace() {
|
||||||
return $message;
|
return $message;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<form
|
<ChatSubmitMessage />
|
||||||
onSubmit={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const trimmed = text.slice(0, 1000).trim();
|
|
||||||
if (trimmed) {
|
|
||||||
// socket.send(new ActionPacket(chat({owner: playerId, text: trimmed}, {game: id})));
|
|
||||||
}
|
|
||||||
setText('');
|
|
||||||
}}
|
|
||||||
ref={$form}
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
className="chat--messagesTextarea"
|
|
||||||
name="message"
|
|
||||||
type="textarea"
|
|
||||||
maxLength="1000"
|
|
||||||
onChange={(event) => {
|
|
||||||
setText(event.target.value);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if ('Enter' === event.key && !event.shiftKey) {
|
|
||||||
$form.current?.dispatchEvent(new Event('submit', {cancelable: true}));
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={text}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
56
src/client/chat--submitMessage.jsx
Normal file
56
src/client/chat--submitMessage.jsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import './chat--messages.scss';
|
||||||
|
|
||||||
|
import React, {useRef, useState} from 'react';
|
||||||
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
|
import {addMessage} from '~/common/state/chat';
|
||||||
|
import {userSelector} from '~/common/state/user';
|
||||||
|
|
||||||
|
import useChannel from '~/client/hooks/useChannel';
|
||||||
|
|
||||||
|
export default function ChatSubmitMessage() {
|
||||||
|
const channel = useChannel();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const $form = useRef(null);
|
||||||
|
const user = useSelector(userSelector);
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
return (
|
||||||
|
<div className="chat--messageSubmit">
|
||||||
|
<form
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const message = text.slice(0, 1000).trim();
|
||||||
|
if (message) {
|
||||||
|
dispatch(addMessage({
|
||||||
|
channel,
|
||||||
|
message,
|
||||||
|
owner: user.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
uuid: uuidv4(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
setText('');
|
||||||
|
}}
|
||||||
|
ref={$form}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
className="chat--messagesTextarea"
|
||||||
|
name="message"
|
||||||
|
type="textarea"
|
||||||
|
maxLength="1000"
|
||||||
|
onChange={(event) => {
|
||||||
|
setText(event.target.value);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if ('Enter' === event.key && !event.shiftKey) {
|
||||||
|
$form.current?.dispatchEvent(new Event('submit', {cancelable: true}));
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={text}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
0
src/client/chat--submitMessage.scss
Normal file
0
src/client/chat--submitMessage.scss
Normal file
6
src/client/hooks/useChannel.js
Normal file
6
src/client/hooks/useChannel.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import {useLocation} from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function useChannel() {
|
||||||
|
const {pathname} = useLocation();
|
||||||
|
return pathname.match(/^\/chat\//) ? pathname.substr('/chat'.length) : '';
|
||||||
|
}
|
|
@ -2,9 +2,8 @@ import {useEffect} from 'react';
|
||||||
|
|
||||||
import {SocketClient} from '@avocado/net/client/socket';
|
import {SocketClient} from '@avocado/net/client/socket';
|
||||||
|
|
||||||
const frontendOrigin = window.location.href;
|
const isSecure = 'https:' === window.location.protocol;
|
||||||
const isSecure = 'https' === frontendOrigin.substr(0, 5);
|
export const socket = new SocketClient(window.location.host, {secure: isSecure});
|
||||||
export const socket = new SocketClient(frontendOrigin, {secure: isSecure});
|
|
||||||
|
|
||||||
export default function useSocket(fn) {
|
export default function useSocket(fn) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
|
@ -39,10 +39,34 @@ q:before, q:after {
|
||||||
content: '';
|
content: '';
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
}
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
font-size: 1.05em;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
22
src/client/store/effects.js
vendored
Normal file
22
src/client/store/effects.js
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Message from '~/common/packets/message.packet';
|
||||||
|
import {addMessage, confirmMessage} from '~/common/state/chat';
|
||||||
|
|
||||||
|
import {socket} from '~/client/hooks/useSocket';
|
||||||
|
|
||||||
|
const effects = {
|
||||||
|
[addMessage]: ({dispatch}, {payload}) => {
|
||||||
|
socket.send(new Message(payload), ([timestamp, current]) => {
|
||||||
|
dispatch(confirmMessage({current, previous: payload.uuid, timestamp}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const middleware = (store) => (next) => (action) => {
|
||||||
|
const result = next(action);
|
||||||
|
if (effects[action.type]) {
|
||||||
|
setTimeout(() => effects[action.type](store, action), 0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default effects;
|
|
@ -5,6 +5,8 @@ import chat from '~/common/state/chat';
|
||||||
import user from '~/common/state/user';
|
import user from '~/common/state/user';
|
||||||
import createCommonStore from '~/common/store';
|
import createCommonStore from '~/common/store';
|
||||||
|
|
||||||
|
import {middleware as effectsMiddleware} from './effects';
|
||||||
|
|
||||||
const reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
chat,
|
chat,
|
||||||
user,
|
user,
|
||||||
|
@ -15,7 +17,7 @@ export default function createStore(options = {}) {
|
||||||
merge(
|
merge(
|
||||||
options,
|
options,
|
||||||
{
|
{
|
||||||
middleware: [],
|
middleware: [effectsMiddleware],
|
||||||
reducer,
|
reducer,
|
||||||
},
|
},
|
||||||
),
|
),
|
|
@ -5,7 +5,10 @@ export default class Message extends Packet {
|
||||||
static get schema() {
|
static get schema() {
|
||||||
return {
|
return {
|
||||||
...super.schema,
|
...super.schema,
|
||||||
data: {},
|
data: {
|
||||||
|
channel: 'string',
|
||||||
|
message: 'string',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,19 @@ import {
|
||||||
|
|
||||||
import hydration from './hydration';
|
import hydration from './hydration';
|
||||||
|
|
||||||
export const userSelector = (state) => state.user;
|
export const channelsSelector = (state) => state.chat.channels;
|
||||||
|
|
||||||
|
export const channelSelector = createSelector(
|
||||||
|
[channelsSelector, (_, channel) => channel],
|
||||||
|
(channels, channel) => channels[channel],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const messagesSelector = (state) => state.chat.messages;
|
||||||
|
|
||||||
|
export const channelMessagesSelector = createSelector(
|
||||||
|
[channelSelector, messagesSelector],
|
||||||
|
(channel, messages) => channel.messages.map((uuid) => messages[uuid]),
|
||||||
|
);
|
||||||
|
|
||||||
const slice = createSlice({
|
const slice = createSlice({
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
|
@ -20,35 +32,48 @@ const slice = createSlice({
|
||||||
...(hydration('chat') || {}),
|
...(hydration('chat') || {}),
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
addMessage: (state, {payload: {channel, message}}) => {
|
addMessage: ({
|
||||||
state.messages.push(message);
|
channels,
|
||||||
state.channels[channel].messages.push(message.uuid);
|
focus,
|
||||||
if (state.focus !== channel) {
|
messages,
|
||||||
state.unread[channel] += 1;
|
unread,
|
||||||
|
}, {payload}) => {
|
||||||
|
const {channel, uuid} = payload;
|
||||||
|
messages[uuid] = payload;
|
||||||
|
channels[channel].messages.push(uuid);
|
||||||
|
if (focus !== channel) {
|
||||||
|
unread[channel] += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addRecent: (state, {payload: {channel}}) => {
|
addRecent: ({recent}, {payload: {channel}}) => {
|
||||||
state.recent.push(channel);
|
recent.push(channel);
|
||||||
},
|
},
|
||||||
editMessage: (state, {payload: {uuid, content}}) => {
|
confirmMessage: ({channels, messages}, {payload: {previous, current, timestamp}}) => {
|
||||||
state.messages[uuid] = content;
|
messages[current] = {...messages[previous], timestamp, uuid: current};
|
||||||
|
delete messages[previous];
|
||||||
|
const {[messages[current].channel]: channel} = channels;
|
||||||
|
const index = channel.messages.findIndex((uuid) => uuid === previous);
|
||||||
|
channel.messages[index] = current;
|
||||||
},
|
},
|
||||||
focus: (state, {payload: {channel}}) => {
|
editMessage: ({messages}, {payload: {uuid, message}}) => {
|
||||||
state.unread[channel] = 0;
|
messages[uuid].message = message;
|
||||||
},
|
},
|
||||||
join: (state, {payload: {channel, messages, users}}) => {
|
focus: ({unread}, {payload: {channel}}) => {
|
||||||
state.channels[channel] = {messages, users};
|
unread[channel] = 0;
|
||||||
state.unread[channel] = 0;
|
|
||||||
},
|
},
|
||||||
joined: (state, {payload: {channel, id}}) => {
|
join: ({channels, unread}, {payload: {channel, messages, users}}) => {
|
||||||
state.channels[channel].users.push(id);
|
channels[channel] = {messages, users};
|
||||||
|
unread[channel] = 0;
|
||||||
},
|
},
|
||||||
leave: (state, {payload: {channel}}) => {
|
joined: ({channels}, {payload: {channel, id}}) => {
|
||||||
delete state.channels[channel];
|
channels[channel].users.push(id);
|
||||||
delete state.unread[channel];
|
|
||||||
},
|
},
|
||||||
left: (state, {payload: {channel, id}}) => {
|
leave: ({channels, unread}, {payload: {channel}}) => {
|
||||||
const {users} = state.channels[channel];
|
delete channels[channel];
|
||||||
|
delete unread[channel];
|
||||||
|
},
|
||||||
|
left: ({channels}, {payload: {channel, id}}) => {
|
||||||
|
const {users} = channels[channel];
|
||||||
users.splice(users.indexOf(id), 1);
|
users.splice(users.indexOf(id), 1);
|
||||||
},
|
},
|
||||||
removeMessage: (state, {payload: {channel, uuid}}) => {
|
removeMessage: (state, {payload: {channel, uuid}}) => {
|
||||||
|
@ -56,8 +81,7 @@ const slice = createSlice({
|
||||||
const {messages} = state.channels[channel];
|
const {messages} = state.channels[channel];
|
||||||
messages.splice(messages.indexOf(uuid), 1);
|
messages.splice(messages.indexOf(uuid), 1);
|
||||||
},
|
},
|
||||||
removeRecent: (state, {payload: {channel}}) => {
|
removeRecent: ({recent}, {payload: {channel}}) => {
|
||||||
const {recent} = state;
|
|
||||||
recent.splice(recent.indexOf(channel), 1);
|
recent.splice(recent.indexOf(channel), 1);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -65,9 +89,14 @@ const slice = createSlice({
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
addMessage,
|
addMessage,
|
||||||
|
addRecent,
|
||||||
|
confirmMessage,
|
||||||
editMessage,
|
editMessage,
|
||||||
|
focus,
|
||||||
join,
|
join,
|
||||||
|
joined,
|
||||||
leave,
|
leave,
|
||||||
|
left,
|
||||||
removeMessage,
|
removeMessage,
|
||||||
removeRecent,
|
removeRecent,
|
||||||
} = slice.actions;
|
} = slice.actions;
|
||||||
|
|
|
@ -34,6 +34,7 @@ const slice = createSlice({
|
||||||
blocked: [],
|
blocked: [],
|
||||||
favorites: [],
|
favorites: [],
|
||||||
friendship: [],
|
friendship: [],
|
||||||
|
id: 0,
|
||||||
isAnonymous: false,
|
isAnonymous: false,
|
||||||
redditUsername: 'anonymous',
|
redditUsername: 'anonymous',
|
||||||
...(hydration('user') || {isAnonymous: true}),
|
...(hydration('user') || {isAnonymous: true}),
|
||||||
|
|
37
src/server/entry.js
Normal file
37
src/server/entry.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import createRedisClient from './redis';
|
||||||
|
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export const enterChannel = async (channel) => {
|
||||||
|
const messages = await new Promise((resolve, reject) => {
|
||||||
|
redisClient.scan(
|
||||||
|
0,
|
||||||
|
'COUNT', 50,
|
||||||
|
'MATCH', `${channel}:*`,
|
||||||
|
(error, [, keys]) => (
|
||||||
|
error
|
||||||
|
? reject(error)
|
||||||
|
: resolve(
|
||||||
|
0 === keys.length
|
||||||
|
? []
|
||||||
|
: new Promise((resolve, reject) => {
|
||||||
|
redisClient.mget(keys, (error, replies) => (
|
||||||
|
error
|
||||||
|
? reject(error)
|
||||||
|
: resolve(replies
|
||||||
|
.map((reply, i) => ({
|
||||||
|
...JSON.parse(reply),
|
||||||
|
uuid: keys[i].split(':')[1],
|
||||||
|
}))
|
||||||
|
.sort((l, r) => l.timestamp - r.timestamp))
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
};
|
8
src/server/redis.js
Normal file
8
src/server/redis.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import redis from 'redis';
|
||||||
|
|
||||||
|
const {
|
||||||
|
REDIS_HOST,
|
||||||
|
REDIS_PORT,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
export default () => redis.createClient(REDIS_PORT, REDIS_HOST);
|
|
@ -1,14 +1,10 @@
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import redis from 'redis';
|
|
||||||
import {registerHooks} from 'scwp';
|
import {registerHooks} from 'scwp';
|
||||||
|
|
||||||
const {
|
import {enterChannel} from './entry';
|
||||||
REDIS_HOST,
|
import createRedisClient from './redis';
|
||||||
REDIS_PORT,
|
|
||||||
} = process.env;
|
|
||||||
|
|
||||||
const redisClient = redis.createClient(REDIS_PORT, REDIS_HOST);
|
|
||||||
|
|
||||||
|
const redisClient = createRedisClient();
|
||||||
// eslint-disable-next-line import/newline-after-import
|
// eslint-disable-next-line import/newline-after-import
|
||||||
const RedisStore = require('connect-redis')(session);
|
const RedisStore = require('connect-redis')(session);
|
||||||
|
|
||||||
|
@ -54,11 +50,14 @@ registerHooks({
|
||||||
messages: {},
|
messages: {},
|
||||||
recent: [],
|
recent: [],
|
||||||
};
|
};
|
||||||
|
const entries = await Promise.all(
|
||||||
|
favorites.map((favorite) => enterChannel(channelName(favorite))),
|
||||||
|
);
|
||||||
for (let i = 0; i < favorites.length; i++) {
|
for (let i = 0; i < favorites.length; i++) {
|
||||||
const channel = channelName(favorites[i]);
|
const channel = channelName(favorites[i]);
|
||||||
const messages = [];
|
const {messages} = entries[i];
|
||||||
chat.channels[channel] = {
|
chat.channels[channel] = {
|
||||||
messages,
|
messages: messages.map((message) => message.uuid),
|
||||||
users: [],
|
users: [],
|
||||||
};
|
};
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
import redisAdapter from 'socket.io-redis';
|
import redisAdapter from 'socket.io-redis';
|
||||||
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
import {SocketServer} from '@avocado/net/server/socket';
|
import {SocketServer} from '@avocado/net/server/socket';
|
||||||
import socketSession from 'express-socket.io-session';
|
import socketSession from 'express-socket.io-session';
|
||||||
|
|
||||||
|
import Message from '~/common/packets/message.packet';
|
||||||
|
|
||||||
import passport from './passport';
|
import passport from './passport';
|
||||||
|
import createRedisClient from './redis';
|
||||||
import session from './session';
|
import session from './session';
|
||||||
|
|
||||||
const {
|
const pubClient = createRedisClient();
|
||||||
REDIS_HOST,
|
const subClient = createRedisClient();
|
||||||
REDIS_PORT,
|
|
||||||
} = process.env;
|
|
||||||
|
|
||||||
export function createSocketServer(httpServer) {
|
export function createSocketServer(httpServer) {
|
||||||
const socketServer = new SocketServer(httpServer, {
|
const socketServer = new SocketServer(httpServer, {
|
||||||
adapter: redisAdapter({host: REDIS_HOST, port: REDIS_PORT}),
|
adapter: redisAdapter({pubClient, subClient}),
|
||||||
});
|
});
|
||||||
socketServer.io.use(socketSession(session()));
|
socketServer.io.use(socketSession(session()));
|
||||||
socketServer.io.use((socket, next) => {
|
socketServer.io.use((socket, next) => {
|
||||||
|
@ -26,6 +28,22 @@ export function createSocketServer(httpServer) {
|
||||||
socketServer.on('connect', (socket) => {
|
socketServer.on('connect', (socket) => {
|
||||||
const {req} = socket;
|
const {req} = socket;
|
||||||
socket.on('packet', (packet, fn) => {
|
socket.on('packet', (packet, fn) => {
|
||||||
|
if (packet instanceof Message) {
|
||||||
|
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()
|
||||||
|
.set(key, JSON.stringify({
|
||||||
|
message,
|
||||||
|
owner,
|
||||||
|
timestamp,
|
||||||
|
}))
|
||||||
|
.expire(key, 600)
|
||||||
|
.exec(() => fn([timestamp, uuid]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return socketServer;
|
return socketServer;
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -9656,16 +9656,16 @@ utils-merge@1.0.1, utils-merge@1.x.x:
|
||||||
resolved "https://npm.i12e.cha0s.io/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://npm.i12e.cha0s.io/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
uuid@8.2.0, uuid@^8.1.0:
|
||||||
|
version "8.2.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
|
||||||
|
integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
|
||||||
|
|
||||||
uuid@^3.3.2, uuid@^3.4.0:
|
uuid@^3.3.2, uuid@^3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
uuid@^8.1.0:
|
|
||||||
version "8.2.0"
|
|
||||||
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
|
|
||||||
integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
|
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
|
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
|
resolved "https://npm.i12e.cha0s.io/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user