refactor socket events
send captcha solution via websocket
This commit is contained in:
parent
7a2053fda3
commit
e9952134a5
|
@ -8,11 +8,6 @@ import createKeyPressHandler from './controls/keypress';
|
|||
import {
|
||||
initTimer,
|
||||
urlChange,
|
||||
receiveOnline,
|
||||
receiveCoolDown,
|
||||
receiveChatMessage,
|
||||
addChatChannel,
|
||||
removeChatChannel,
|
||||
setMobile,
|
||||
windowResize,
|
||||
} from './store/actions';
|
||||
|
@ -47,50 +42,6 @@ persistStore(store, {}, () => {
|
|||
SocketClient.on('pixelReturn', (args) => {
|
||||
receivePixelReturn(store, getRenderer(), args);
|
||||
});
|
||||
SocketClient.on('cooldownPacket', (coolDown) => {
|
||||
store.dispatch(receiveCoolDown(coolDown));
|
||||
});
|
||||
SocketClient.on('onlineCounter', (online) => {
|
||||
store.dispatch(receiveOnline(online));
|
||||
});
|
||||
SocketClient.on('chatMessage', (
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
) => {
|
||||
const state = store.getState();
|
||||
|
||||
// assume that if one chat window is not hidden, all are
|
||||
const isRead = state.windows.showWindows
|
||||
// eslint-disable-next-line max-len
|
||||
&& state.windows.windows.find((win) => win.windowType === 'CHAT' && !win.hidden)
|
||||
// eslint-disable-next-line max-len
|
||||
&& Object.values(state.windows.args).find((args) => args.chatChannel === channelId);
|
||||
|
||||
// TODO ping doesn't work since update
|
||||
// const { nameRegExp } = state.user;
|
||||
// const isPing = (nameRegExp && text.match(nameRegExp));
|
||||
store.dispatch(receiveChatMessage(
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
false,
|
||||
!!isRead,
|
||||
));
|
||||
});
|
||||
SocketClient.on('changedMe', () => {
|
||||
store.dispatch(fetchMe());
|
||||
});
|
||||
SocketClient.on('remch', (cid) => {
|
||||
store.dispatch(removeChatChannel(cid));
|
||||
});
|
||||
SocketClient.on('addch', (channel) => {
|
||||
store.dispatch(addChatChannel(channel));
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
store.dispatch(urlChange());
|
||||
|
|
|
@ -8,13 +8,14 @@ import React, { useState } from 'react';
|
|||
import { t } from 'ttag';
|
||||
|
||||
import Captcha from './Captcha';
|
||||
import socketClient from '../socket/SocketClient';
|
||||
import {
|
||||
requestSolveCaptcha,
|
||||
requestBanMe,
|
||||
} from '../store/actions/fetch';
|
||||
|
||||
const GlobalCaptcha = ({ close }) => {
|
||||
const [errors, setErrors] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [legit, setLegit] = useState(false);
|
||||
// used to be able to force Captcha rerender on error
|
||||
const [captKey, setCaptKey] = useState(Date.now());
|
||||
|
@ -24,7 +25,7 @@ const GlobalCaptcha = ({ close }) => {
|
|||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const text = e.target.captcha.value.slice(0, 6);
|
||||
if (!text || text.length < 4) {
|
||||
if (submitting || !text) {
|
||||
return;
|
||||
}
|
||||
// ----
|
||||
|
@ -36,23 +37,44 @@ const GlobalCaptcha = ({ close }) => {
|
|||
}
|
||||
// ----
|
||||
const captchaid = e.target.captchaid.value;
|
||||
const { errors: resErrors } = await requestSolveCaptcha(
|
||||
text,
|
||||
captchaid,
|
||||
);
|
||||
if (resErrors) {
|
||||
setCaptKey(Date.now());
|
||||
setErrors(resErrors);
|
||||
} else {
|
||||
close();
|
||||
let errorText;
|
||||
try {
|
||||
setSubmitting(true);
|
||||
const retCode = await socketClient
|
||||
.sendCaptchaSolution(text, captchaid);
|
||||
console.log('Captcha return:', retCode);
|
||||
switch (retCode) {
|
||||
case 0:
|
||||
close();
|
||||
return;
|
||||
case 1:
|
||||
errorText = t`You took too long, try again.`;
|
||||
break;
|
||||
case 2:
|
||||
errorText = t`You failed your captcha`;
|
||||
break;
|
||||
case 3:
|
||||
errorText = t`No or invalid captcha text`;
|
||||
break;
|
||||
case 4:
|
||||
errorText = t`No captcha id given`;
|
||||
break;
|
||||
default:
|
||||
errorText = t`Unknown Captcha Error`;
|
||||
}
|
||||
} catch (err) {
|
||||
errorText = `${err.message}`;
|
||||
}
|
||||
setSubmitting(false);
|
||||
setCaptKey(Date.now());
|
||||
setError(errorText);
|
||||
}}
|
||||
>
|
||||
{errors.map((error) => (
|
||||
{(error) && (
|
||||
<p key={error} className="errormessage">
|
||||
<span>{t`Error`}</span>: {error}
|
||||
</p>
|
||||
))}
|
||||
)}
|
||||
<Captcha autoload key={captKey} setLegit={setLegit} />
|
||||
<p>
|
||||
<button
|
||||
|
@ -65,7 +87,7 @@ const GlobalCaptcha = ({ close }) => {
|
|||
<button
|
||||
type="submit"
|
||||
>
|
||||
{t`Send`}
|
||||
{(submitting) ? '...' : t`Send`}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
|
|
|
@ -233,8 +233,7 @@ class PixelPlainterControls {
|
|||
// 5275_-8713
|
||||
// 5398_-8614
|
||||
if (x > 5275 && y > -8713 && x < 5398 && y < -8614) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (state.canvas.canvasId == 0) {
|
||||
if (state.canvas.canvasId === '0') {
|
||||
window.location.href = 'https://files.catbox.moe/gh2wtr.mp4';
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -35,8 +35,7 @@ function createKeyPressHandler(store) {
|
|||
const canvasIds = Object.keys(canvases).filter((id) => !canvases[id].hid);
|
||||
if (num <= canvasIds.length) {
|
||||
const canvasId = canvasIds[num - 1];
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (canvasId != curCanvasId) {
|
||||
if (canvasId !== curCanvasId) {
|
||||
store.dispatch(selectCanvas(canvasId));
|
||||
const canvasName = canvases[canvasId].title;
|
||||
store.dispatch(notify(t`Switched to ${canvasName}`));
|
||||
|
|
|
@ -268,7 +268,7 @@ export class ChatProvider {
|
|||
const cmdArr = message.split(' ');
|
||||
const cmd = cmdArr[0].substring(1);
|
||||
const args = cmdArr.slice(1);
|
||||
const initiator = `@[${escapeMd(user.getName())}](${user.id})`;
|
||||
const initiator = `@[${escapeMd(user.name)}](${user.id})`;
|
||||
switch (cmd) {
|
||||
case 'mute': {
|
||||
const timeMin = Number(args.slice(-1));
|
||||
|
@ -398,7 +398,7 @@ export class ChatProvider {
|
|||
) {
|
||||
const { id } = user;
|
||||
const { t } = user.ttag;
|
||||
const name = user.getName();
|
||||
const { name } = user;
|
||||
|
||||
if (!name || !id) {
|
||||
return null;
|
||||
|
|
|
@ -11,7 +11,7 @@ export const DEFAULT_SCALE = 3;
|
|||
|
||||
// default canvas that is first assumed, before real canvas data
|
||||
// gets fetched via api/me
|
||||
export const DEFAULT_CANVAS_ID = 0;
|
||||
export const DEFAULT_CANVAS_ID = '0';
|
||||
export const DEFAULT_CANVASES = {
|
||||
0: {
|
||||
ident: 'd',
|
||||
|
|
|
@ -160,7 +160,7 @@ export function getIdFromObject(obj, ident) {
|
|||
for (let i = 0; i < ids.length; i += 1) {
|
||||
const key = ids[i];
|
||||
if (obj[key].ident === ident) {
|
||||
return parseInt(key, 10);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -158,6 +158,14 @@ class User {
|
|||
this.setRegUser(this.regUser);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return (this.regUser) ? this.regUser.name : null;
|
||||
}
|
||||
|
||||
get isRegistered() {
|
||||
return !!this.id;
|
||||
}
|
||||
|
||||
addChannel(cid, channelArray) {
|
||||
this.channels[cid] = channelArray;
|
||||
}
|
||||
|
@ -166,10 +174,6 @@ class User {
|
|||
delete this.channels[cid];
|
||||
}
|
||||
|
||||
getName() {
|
||||
return (this.regUser) ? this.regUser.name : null;
|
||||
}
|
||||
|
||||
setWait(wait, canvasId) {
|
||||
return setCoolDown(this.ipSub, this.id, canvasId, wait);
|
||||
}
|
||||
|
|
|
@ -68,20 +68,14 @@ function evaluateResult(captchaText, userText) {
|
|||
* set captcha solution
|
||||
*
|
||||
* @param text Solution of captcha
|
||||
* @param ip
|
||||
* @param captchaid
|
||||
*/
|
||||
export async function setCaptchaSolution(
|
||||
text,
|
||||
ip,
|
||||
captchaid = null,
|
||||
captchaid,
|
||||
) {
|
||||
let key = `capt:${ip}`;
|
||||
if (captchaid) {
|
||||
key += `:${captchaid}`;
|
||||
}
|
||||
try {
|
||||
await client.set(key, text, {
|
||||
await client.set(`capt:${captchaid}`, text, {
|
||||
EX: CAPTCHA_TIMEOUT,
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -105,22 +99,24 @@ export async function setCaptchaSolution(
|
|||
export async function checkCaptchaSolution(
|
||||
text,
|
||||
ip,
|
||||
onetime = false,
|
||||
captchaid = null,
|
||||
onetime,
|
||||
captchaid,
|
||||
wrongCallback = null,
|
||||
) {
|
||||
const ipn = getIPv6Subnet(ip);
|
||||
let key = `capt:${ip}`;
|
||||
if (captchaid) {
|
||||
key += `:${captchaid}`;
|
||||
if (!text || text.length > 10) {
|
||||
return 3;
|
||||
}
|
||||
const solution = await client.get(key);
|
||||
if (!captchaid) {
|
||||
return 4;
|
||||
}
|
||||
const solution = await client.get(`capt:${captchaid}`);
|
||||
if (solution) {
|
||||
if (evaluateResult(solution, text)) {
|
||||
if (Math.random() < 0.1) {
|
||||
return 2;
|
||||
}
|
||||
if (!onetime) {
|
||||
const ipn = getIPv6Subnet(ip);
|
||||
const solvkey = `${PREFIX}:${ipn}`;
|
||||
await client.set(solvkey, '', {
|
||||
EX: TTL_CACHE,
|
||||
|
|
43
src/popup.js
43
src/popup.js
|
@ -8,10 +8,6 @@ import { parentExists } from './core/utils';
|
|||
import store from './store/storePopUp';
|
||||
import {
|
||||
urlChange,
|
||||
receiveOnline,
|
||||
receiveChatMessage,
|
||||
removeChatChannel,
|
||||
addChatChannel,
|
||||
} from './store/actions';
|
||||
import {
|
||||
fetchMe,
|
||||
|
@ -28,45 +24,6 @@ persistStore(store, {}, () => {
|
|||
store.dispatch(urlChange());
|
||||
});
|
||||
|
||||
SocketClient.on('onlineCounter', (online) => {
|
||||
store.dispatch(receiveOnline(online));
|
||||
});
|
||||
SocketClient.on('chatMessage', (
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
) => {
|
||||
const state = store.getState();
|
||||
|
||||
// assume that if one chat window is not hidden, all are
|
||||
const isRead = state.popup.windowType === 'CHAT'
|
||||
&& state.popup.args.chatChannel === channelId;
|
||||
|
||||
// TODO ping doesn't work since update
|
||||
// const { nameRegExp } = state.user;
|
||||
// const isPing = (nameRegExp && text.match(nameRegExp));
|
||||
store.dispatch(receiveChatMessage(
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
false,
|
||||
!!isRead,
|
||||
));
|
||||
});
|
||||
SocketClient.on('changedMe', () => {
|
||||
store.dispatch(fetchMe());
|
||||
});
|
||||
SocketClient.on('remch', (cid) => {
|
||||
store.dispatch(removeChatChannel(cid));
|
||||
});
|
||||
SocketClient.on('addch', (channel) => {
|
||||
store.dispatch(addChatChannel(channel));
|
||||
});
|
||||
|
||||
if (!parentExists()) {
|
||||
store.dispatch(fetchMe());
|
||||
SocketClient.initialize(store);
|
||||
|
|
|
@ -119,7 +119,7 @@ async function block(req, res) {
|
|||
errors: ['Could not (un)block user'],
|
||||
});
|
||||
logger.info(
|
||||
`User ${user.getName()} (un)blocked ${userName}`,
|
||||
`User ${user.name} (un)blocked ${userName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ async function blockdm(req, res) {
|
|||
}
|
||||
|
||||
logger.info(
|
||||
`User ${user.getName()} (un)blocked all dms`,
|
||||
`User ${user.name} (un)blocked all dms`,
|
||||
);
|
||||
|
||||
await user.regUser.update({
|
||||
|
|
|
@ -15,16 +15,6 @@ export default async (req, res) => {
|
|||
|
||||
try {
|
||||
const { text, id } = req.body;
|
||||
if (!text) {
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha text given`] });
|
||||
return;
|
||||
}
|
||||
if (!id) {
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha id given`] });
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = await checkCaptchaSolution(
|
||||
text,
|
||||
|
@ -46,15 +36,19 @@ export default async (req, res) => {
|
|||
break;
|
||||
case 2:
|
||||
res.status(422)
|
||||
.json({
|
||||
errors: [t`You failed your captcha`],
|
||||
});
|
||||
.json({ errors: [t`You failed your captcha`] });
|
||||
break;
|
||||
case 3:
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha text given`] });
|
||||
break;
|
||||
case 4:
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha id given`] });
|
||||
break;
|
||||
default:
|
||||
res.status(422)
|
||||
.json({
|
||||
errors: [t`Unknown Captcha Error`],
|
||||
});
|
||||
.json({ errors: [t`Unknown Captcha Error`] });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('CAPTCHA', error);
|
||||
|
|
|
@ -57,7 +57,7 @@ async function leaveChan(req, res) {
|
|||
}
|
||||
|
||||
logger.info(
|
||||
`Removing user ${user.getName()} from channel ${channel.name || channelId}`,
|
||||
`Removing user ${user.name} from channel ${channel.name || channelId}`,
|
||||
);
|
||||
|
||||
user.regUser.removeChannel(channel);
|
||||
|
|
|
@ -25,7 +25,7 @@ async function privatize(req, res) {
|
|||
}
|
||||
|
||||
logger.info(
|
||||
`User ${user.getName()} set private status to ${priv}`,
|
||||
`User ${user.name} set private status to ${priv}`,
|
||||
);
|
||||
|
||||
await user.regUser.update({
|
||||
|
|
|
@ -108,7 +108,7 @@ async function startDm(req, res) {
|
|||
ChatProvider.addUserToChannel(
|
||||
userId,
|
||||
ChannelId,
|
||||
[user.getName(), 1, curTime, user.id],
|
||||
[user.name, 1, curTime, user.id],
|
||||
),
|
||||
];
|
||||
await Promise.all(promises);
|
||||
|
|
|
@ -27,7 +27,7 @@ export default (req, res) => {
|
|||
}
|
||||
|
||||
const ip = getIPFromRequest(req);
|
||||
setCaptchaSolution(text, ip, id);
|
||||
setCaptchaSolution(text, id);
|
||||
logger.info(`Captchas: ${ip} got captcha with text: ${text}`);
|
||||
|
||||
res.set({
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import WebSocket from 'ws';
|
||||
|
||||
import socketEvents from './socketEvents';
|
||||
import { dehydrateOnlineCounter } from './packets/server';
|
||||
import chatProvider, { ChatProvider } from '../core/ChatProvider';
|
||||
import { RegUser } from '../data/sql';
|
||||
import { getIPFromRequest } from '../utils/ip';
|
||||
|
@ -55,7 +56,6 @@ class APISocketServer {
|
|||
});
|
||||
});
|
||||
|
||||
this.broadcast = this.broadcast.bind(this);
|
||||
this.broadcastOnlineCounter = this.broadcastOnlineCounter.bind(this);
|
||||
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
|
||||
this.ping = this.ping.bind(this);
|
||||
|
@ -155,8 +155,9 @@ class APISocketServer {
|
|||
}
|
||||
}
|
||||
|
||||
broadcastOnlineCounter(data) {
|
||||
this.broadcast(data, (client) => client.subOnline);
|
||||
broadcastOnlineCounter(online) {
|
||||
const buffer = dehydrateOnlineCounter(online);
|
||||
this.broadcast(buffer, (client) => client.subOnline);
|
||||
}
|
||||
|
||||
broadcastPixelBuffer(canvasId, chunkid, buffer) {
|
||||
|
|
|
@ -371,11 +371,10 @@ class MessageBroker extends SocketEvents {
|
|||
broadcastOnlineCounter(online) {
|
||||
this.updateShardOnlineCounter(this.thisShard, online);
|
||||
// send our online counter to other shards
|
||||
let buffer = dehydrateOnlineCounter(online);
|
||||
const buffer = dehydrateOnlineCounter(online);
|
||||
this.publisher.publish(this.thisShard, buffer);
|
||||
// send total counter to our players
|
||||
buffer = dehydrateOnlineCounter(this.onlineCounter);
|
||||
super.emit('onlineCounter', buffer);
|
||||
super.emit('onlineCounter', this.onlineCounter);
|
||||
}
|
||||
|
||||
checkHealth() {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import EventEmitter from 'events';
|
||||
|
||||
import {
|
||||
dehydrateOnlineCounter,
|
||||
dehydratePixelUpdate,
|
||||
} from './packets/server';
|
||||
|
||||
|
@ -255,8 +254,7 @@ class SocketEvents extends EventEmitter {
|
|||
*/
|
||||
broadcastOnlineCounter(online) {
|
||||
this.onlineCounter = online;
|
||||
const buffer = dehydrateOnlineCounter(online);
|
||||
this.emit('onlineCounter', buffer);
|
||||
this.emit('onlineCounter', online);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ import {
|
|||
hydratePixelReturn,
|
||||
hydrateOnlineCounter,
|
||||
hydrateCoolDown,
|
||||
hydrateCaptchaReturn,
|
||||
dehydrateRegCanvas,
|
||||
dehydrateRegChunk,
|
||||
dehydrateRegMChunks,
|
||||
dehydrateDeRegChunk,
|
||||
dehydrateCaptchaSolution,
|
||||
dehydratePixelUpdate,
|
||||
dehydratePing,
|
||||
} from './packets/client';
|
||||
|
@ -22,7 +22,20 @@ import {
|
|||
ONLINE_COUNTER_OP,
|
||||
COOLDOWN_OP,
|
||||
CHANGE_ME_OP,
|
||||
CAPTCHA_RETURN_OP,
|
||||
} from './packets/op';
|
||||
import {
|
||||
socketOpen,
|
||||
socketClose,
|
||||
receiveOnline,
|
||||
receiveCoolDown,
|
||||
receiveChatMessage,
|
||||
addChatChannel,
|
||||
removeChatChannel,
|
||||
} from '../store/actions/socket';
|
||||
import {
|
||||
fetchMe,
|
||||
} from '../store/actions/thunks';
|
||||
import { shardHost } from '../store/actions/fetch';
|
||||
|
||||
const chunks = [];
|
||||
|
@ -42,6 +55,7 @@ class SocketClient extends EventEmitter {
|
|||
*/
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
this.msgQueue = [];
|
||||
this.reqQueue = [];
|
||||
|
||||
this.checkHealth = this.checkHealth.bind(this);
|
||||
setInterval(this.checkHealth, 2000);
|
||||
|
@ -122,10 +136,10 @@ class SocketClient extends EventEmitter {
|
|||
this.timeLastPing = now;
|
||||
this.timeLastSent = now;
|
||||
|
||||
this.emit('open', {});
|
||||
this.store.dispatch(socketOpen());
|
||||
this.readyState = WebSocket.OPEN;
|
||||
this.send(dehydrateRegCanvas(
|
||||
this.store.getState().canvas,
|
||||
this.store.getState().canvas.canvasId,
|
||||
));
|
||||
console.log(`Register ${chunks.length} chunks`);
|
||||
this.send(dehydrateRegMChunks(chunks));
|
||||
|
@ -136,7 +150,9 @@ class SocketClient extends EventEmitter {
|
|||
if (canvasId === null) {
|
||||
return;
|
||||
}
|
||||
console.log('Notify websocket server that we changed canvas');
|
||||
console.log(
|
||||
`Notify websocket server that we changed canvas to ${canvasId}`,
|
||||
);
|
||||
chunks.length = 0;
|
||||
this.send(dehydrateRegCanvas(canvasId));
|
||||
}
|
||||
|
@ -163,10 +179,28 @@ class SocketClient extends EventEmitter {
|
|||
}
|
||||
|
||||
/*
|
||||
sendCaptchaSolution(solution) {
|
||||
const buffer = dehydrateCaptchaSolution(solution);
|
||||
* send captcha solution
|
||||
* @param solution text
|
||||
* @return promise that resolves when response arrives
|
||||
*/
|
||||
sendCaptchaSolution(solution, captchaid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let id;
|
||||
const queueObj = ['cs', (args) => {
|
||||
resolve(args);
|
||||
clearTimeout(id);
|
||||
}];
|
||||
this.reqQueue.push(queueObj);
|
||||
id = setTimeout(() => {
|
||||
const pos = this.reqQueue.indexOf(queueObj);
|
||||
if (~pos) this.reqQueue.splice(pos, 1);
|
||||
reject(new Error('Timeout'));
|
||||
}, 20000);
|
||||
this.sendWhenReady(
|
||||
`cs,${JSON.stringify([solution, captchaid])}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* Send pixel request
|
||||
|
@ -182,7 +216,9 @@ class SocketClient extends EventEmitter {
|
|||
}
|
||||
|
||||
sendChatMessage(message, channelId) {
|
||||
this.sendWhenReady(JSON.stringify([message, channelId]));
|
||||
this.sendWhenReady(
|
||||
`cm,${JSON.stringify([message, channelId])}`,
|
||||
);
|
||||
}
|
||||
|
||||
onMessage({ data: message }) {
|
||||
|
@ -201,27 +237,24 @@ class SocketClient extends EventEmitter {
|
|||
}
|
||||
|
||||
onTextMessage(message) {
|
||||
if (!message) return;
|
||||
const data = JSON.parse(message);
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
switch (data.length) {
|
||||
case 5: {
|
||||
// chat message
|
||||
const [name, text, country, channelId, userId] = data;
|
||||
this.emit('chatMessage',
|
||||
name, text, country, Number(channelId), userId);
|
||||
return;
|
||||
}
|
||||
case 2: {
|
||||
// signal
|
||||
const [signal, args] = data;
|
||||
this.emit(signal, args);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
const comma = message.indexOf(',');
|
||||
if (comma === -1) {
|
||||
return;
|
||||
}
|
||||
const key = message.slice(0, comma);
|
||||
const val = JSON.parse(message.slice(comma + 1));
|
||||
switch (key) {
|
||||
case 'cm':
|
||||
this.store.dispatch(receiveChatMessage(...val));
|
||||
break;
|
||||
case 'ac':
|
||||
this.store.dispatch(addChatChannel(val));
|
||||
break;
|
||||
case 'rc':
|
||||
this.store.dispatch(removeChatChannel(val));
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,16 +273,23 @@ class SocketClient extends EventEmitter {
|
|||
this.emit('pixelReturn', hydratePixelReturn(data));
|
||||
break;
|
||||
case ONLINE_COUNTER_OP:
|
||||
this.emit('onlineCounter', hydrateOnlineCounter(data));
|
||||
this.store.dispatch(receiveOnline(hydrateOnlineCounter(data)));
|
||||
break;
|
||||
case COOLDOWN_OP:
|
||||
this.emit('cooldownPacket', hydrateCoolDown(data));
|
||||
this.store.dispatch(receiveCoolDown(hydrateCoolDown(data)));
|
||||
break;
|
||||
case CHANGE_ME_OP:
|
||||
console.log('Websocket requested api/me reload');
|
||||
this.emit('changedMe');
|
||||
this.store.dispatch(fetchMe());
|
||||
this.reconnect();
|
||||
break;
|
||||
case CAPTCHA_RETURN_OP: {
|
||||
const pos = this.reqQueue.findIndex((q) => q[0] === 'cs');
|
||||
if (~pos) {
|
||||
this.reqQueue.splice(pos, 1)[0][1](hydrateCaptchaReturn(data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(`Unknown op_code ${opcode} received`);
|
||||
break;
|
||||
|
@ -257,7 +297,7 @@ class SocketClient extends EventEmitter {
|
|||
}
|
||||
|
||||
onClose(e) {
|
||||
this.emit('close');
|
||||
this.store.dispatch(socketClose());
|
||||
this.ws = null;
|
||||
this.readyState = WebSocket.CONNECTING;
|
||||
// reconnect in 1s if last connect was longer than 7s ago, else 5s
|
||||
|
|
|
@ -21,19 +21,19 @@ import {
|
|||
hydrateDeRegChunk,
|
||||
hydrateRegMChunks,
|
||||
hydrateDeRegMChunks,
|
||||
hydrateCaptchaSolution,
|
||||
hydratePixelUpdate,
|
||||
dehydrateChangeMe,
|
||||
dehydrateOnlineCounter,
|
||||
dehydratePixelUpdate,
|
||||
dehydrateCoolDown,
|
||||
dehydratePixelReturn,
|
||||
dehydrateCaptchaReturn,
|
||||
} from './packets/server';
|
||||
import socketEvents from './socketEvents';
|
||||
import chatProvider, { ChatProvider } from '../core/ChatProvider';
|
||||
import authenticateClient from './authenticateClient';
|
||||
import drawByOffsets from '../core/draw';
|
||||
import isIPAllowed from '../core/isAllowed';
|
||||
import { checkCaptchaSolution } from '../data/redis/captcha';
|
||||
|
||||
|
||||
const ipCounter = new Counter();
|
||||
|
@ -64,7 +64,6 @@ class SocketServer {
|
|||
constructor() {
|
||||
this.CHUNK_CLIENTS = new Map();
|
||||
|
||||
this.broadcast = this.broadcast.bind(this);
|
||||
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
|
||||
this.reloadUser = this.reloadUser.bind(this);
|
||||
this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this);
|
||||
|
@ -93,7 +92,6 @@ class SocketServer {
|
|||
ws.canvasId = null;
|
||||
const { user } = req;
|
||||
ws.user = user;
|
||||
ws.name = user.getName();
|
||||
ws.chunkCnt = 0;
|
||||
|
||||
const { ip } = user;
|
||||
|
@ -101,7 +99,8 @@ class SocketServer {
|
|||
ws.send(dehydrateOnlineCounter(socketEvents.onlineCounter));
|
||||
|
||||
ws.on('error', (e) => {
|
||||
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
|
||||
// eslint-disable-next-line max-len
|
||||
logger.error(`WebSocket Client Error for ${ws.user.name}: ${e.message}`);
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
|
@ -120,7 +119,10 @@ class SocketServer {
|
|||
});
|
||||
});
|
||||
|
||||
socketEvents.on('onlineCounter', this.broadcast);
|
||||
socketEvents.on('onlineCounter', (online) => {
|
||||
const onlineBuffer = dehydrateOnlineCounter(online);
|
||||
this.broadcast(onlineBuffer);
|
||||
});
|
||||
socketEvents.on('pixelUpdate', this.broadcastPixelBuffer);
|
||||
socketEvents.on('reloadUser', this.reloadUser);
|
||||
|
||||
|
@ -132,8 +134,10 @@ class SocketServer {
|
|||
id,
|
||||
country,
|
||||
) => {
|
||||
const text = `cm,${JSON.stringify(
|
||||
[name, message, country, channelId, id],
|
||||
)}`;
|
||||
this.findAllWsByUerId(userId).forEach((ws) => {
|
||||
const text = JSON.stringify([name, message, country, channelId, id]);
|
||||
ws.send(text);
|
||||
});
|
||||
});
|
||||
|
@ -145,7 +149,9 @@ class SocketServer {
|
|||
id,
|
||||
country,
|
||||
) => {
|
||||
const text = JSON.stringify([name, message, country, channelId, id]);
|
||||
const text = `cm,${JSON.stringify(
|
||||
[name, message, country, channelId, id],
|
||||
)}`;
|
||||
const clientArray = [];
|
||||
this.wss.clients.forEach((ws) => {
|
||||
if (ws.user && chatProvider.userHasChannelAccess(ws.user, channelId)) {
|
||||
|
@ -158,11 +164,9 @@ class SocketServer {
|
|||
socketEvents.on('addChatChannel', (userId, channelId, channelArray) => {
|
||||
this.findAllWsByUerId(userId).forEach((ws) => {
|
||||
ws.user.addChannel(channelId, channelArray);
|
||||
const text = JSON.stringify([
|
||||
'addch', {
|
||||
[channelId]: channelArray,
|
||||
},
|
||||
]);
|
||||
const text = `ac,${JSON.stringify({
|
||||
[channelId]: channelArray,
|
||||
})}`;
|
||||
ws.send(text);
|
||||
});
|
||||
});
|
||||
|
@ -170,7 +174,7 @@ class SocketServer {
|
|||
socketEvents.on('remChatChannel', (userId, channelId) => {
|
||||
this.findAllWsByUerId(userId).forEach((ws) => {
|
||||
ws.user.removeChannel(channelId);
|
||||
const text = JSON.stringify(['remch', channelId]);
|
||||
const text = `rc,${JSON.stringify(channelId)}`;
|
||||
ws.send(text);
|
||||
});
|
||||
});
|
||||
|
@ -348,9 +352,8 @@ class SocketServer {
|
|||
|
||||
reloadUser(name) {
|
||||
this.wss.clients.forEach(async (ws) => {
|
||||
if (ws.name === name) {
|
||||
if (ws.user.name === name) {
|
||||
await ws.user.reload();
|
||||
ws.name = ws.user.getName();
|
||||
const buffer = dehydrateChangeMe();
|
||||
ws.send(buffer);
|
||||
}
|
||||
|
@ -419,64 +422,66 @@ class SocketServer {
|
|||
* chat messages in [message, channelId] format
|
||||
*/
|
||||
try {
|
||||
let message;
|
||||
let channelId;
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
[message, channelId] = data;
|
||||
channelId = Number(channelId);
|
||||
if (Number.isNaN(channelId)) {
|
||||
throw new Error('NaN');
|
||||
}
|
||||
} catch {
|
||||
logger.warn(
|
||||
`Received unparseable message from ${ws.name} on websocket: ${text}`,
|
||||
);
|
||||
return;
|
||||
const comma = text.indexOf(',');
|
||||
if (comma === -1) {
|
||||
throw new Error('No comma');
|
||||
}
|
||||
message = message.trim();
|
||||
|
||||
/*
|
||||
* just if logged in
|
||||
*/
|
||||
if (ws.name && message) {
|
||||
const { user } = ws;
|
||||
/*
|
||||
* if DM channel, make sure that other user has DM open
|
||||
* (needed because we allow user to leave one-sided
|
||||
* and auto-join on message)
|
||||
*/
|
||||
const dmUserId = chatProvider.checkIfDm(user, channelId);
|
||||
if (dmUserId) {
|
||||
const dmWs = this.findWsByUserId(dmUserId);
|
||||
if (!dmWs
|
||||
|| !chatProvider.userHasChannelAccess(dmWs.user, channelId)
|
||||
) {
|
||||
// TODO this is really ugly
|
||||
// DMS have to be rethought
|
||||
if (!user.addedDM) user.addedDM = [];
|
||||
if (!user.addedDM.includes(dmUserId)) {
|
||||
await ChatProvider.addUserToChannel(
|
||||
dmUserId,
|
||||
channelId,
|
||||
[ws.name, 1, Date.now(), user.id],
|
||||
);
|
||||
user.addedDM.push(dmUserId);
|
||||
const key = text.slice(0, comma);
|
||||
const val = JSON.parse(text.slice(comma + 1));
|
||||
const { user } = ws;
|
||||
switch (key) {
|
||||
case 'cm': {
|
||||
// chat message
|
||||
const message = val[0].trim();
|
||||
if (!user.isRegistered || !message) {
|
||||
return;
|
||||
}
|
||||
const channelId = val[1];
|
||||
/*
|
||||
* if DM channel, make sure that other user has DM open
|
||||
* (needed because we allow user to leave one-sided
|
||||
* and auto-join on message)
|
||||
*/
|
||||
const dmUserId = chatProvider.checkIfDm(user, channelId);
|
||||
if (dmUserId) {
|
||||
const dmWs = this.findWsByUserId(dmUserId);
|
||||
if (!dmWs
|
||||
|| !chatProvider.userHasChannelAccess(dmWs.user, channelId)
|
||||
) {
|
||||
// TODO this is really ugly
|
||||
// DMS have to be rethought
|
||||
if (!user.addedDM) user.addedDM = [];
|
||||
if (!user.addedDM.includes(dmUserId)) {
|
||||
await ChatProvider.addUserToChannel(
|
||||
dmUserId,
|
||||
channelId,
|
||||
[user.name, 1, Date.now(), user.id],
|
||||
);
|
||||
user.addedDM.push(dmUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
socketEvents.recvChatMessage(user, message, channelId);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* send chat message
|
||||
*/
|
||||
socketEvents.recvChatMessage(user, message, channelId);
|
||||
} else {
|
||||
logger.info('Got empty message or message from unidentified ws');
|
||||
case 'cs': {
|
||||
// captcha solution
|
||||
const [solution, captchaid] = val;
|
||||
const ret = await checkCaptchaSolution(
|
||||
solution,
|
||||
user.ip,
|
||||
false,
|
||||
captchaid,
|
||||
);
|
||||
ws.send(dehydrateCaptchaReturn(ret));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown key');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Got invalid ws text message');
|
||||
logger.error(error.message);
|
||||
logger.error(error.stack);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line max-len
|
||||
logger.error(`Got invalid ws text message ${text}, with error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
DEREG_CHUNK_OP,
|
||||
REG_MCHUNKS_OP,
|
||||
DEREG_MCHUNKS_OP,
|
||||
CAPTCHA_SOLUTION_OP,
|
||||
PING_OP,
|
||||
PIXEL_UPDATE_OP,
|
||||
} from './op';
|
||||
|
@ -70,7 +69,6 @@ export function hydrateCoolDown(data) {
|
|||
* @return see ui/placePixels
|
||||
*/
|
||||
export function hydratePixelReturn(data) {
|
||||
// Client (receiver)
|
||||
const retCode = data.getUint8(1);
|
||||
const wait = data.getUint32(2);
|
||||
const coolDownSeconds = data.getInt16(6);
|
||||
|
@ -85,6 +83,13 @@ export function hydratePixelReturn(data) {
|
|||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* @return code of captcha success
|
||||
*/
|
||||
export function hydrateCaptchaReturn(data) {
|
||||
return data.getUint8(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* dehydrate functions return ArrayBuffer object
|
||||
*/
|
||||
|
@ -152,18 +157,6 @@ export function dehydrateDeRegMChunks(chunks) {
|
|||
return buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param solution string of entered captcha
|
||||
*/
|
||||
export function dehydrateCaptchaSolution(solution) {
|
||||
const encoder = new TextEncoder();
|
||||
const view = encoder.encode(solution);
|
||||
const buffer = new Uint8Array(view.byteLength + 1);
|
||||
buffer[0] = CAPTCHA_SOLUTION_OP;
|
||||
buffer.set(view, 1);
|
||||
return buffer.buffer;
|
||||
}
|
||||
|
||||
export function dehydratePing() {
|
||||
return new Uint8Array([PING_OP]).buffer;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ export const REG_CHUNK_OP = 0xA1;
|
|||
export const DEREG_CHUNK_OP = 0xA2;
|
||||
export const REG_MCHUNKS_OP = 0xA3;
|
||||
export const DEREG_MCHUNKS_OP = 0xA4;
|
||||
export const CAPTCHA_SOLUTION_OP = 0xA5;
|
||||
export const CHANGE_ME_OP = 0xA6;
|
||||
export const ONLINE_COUNTER_OP = 0xA7;
|
||||
export const PING_OP = 0xB0;
|
||||
export const PIXEL_UPDATE_OP = 0xC1;
|
||||
export const PIXEL_UPDATE_MB_OP = 0xC1;
|
||||
export const COOLDOWN_OP = 0xC2;
|
||||
export const PIXEL_RETURN_OP = 0xC3;
|
||||
export const CHUNK_UPDATE_MB_OP = 0xC4;
|
||||
export const PIXEL_UPDATE_MB_OP = 0xC5;
|
||||
export const CAPTCHA_RETURN_OP = 0xC6;
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
COOLDOWN_OP,
|
||||
PIXEL_RETURN_OP,
|
||||
CHUNK_UPDATE_MB_OP,
|
||||
CAPTCHA_RETURN_OP,
|
||||
} from './op';
|
||||
|
||||
/*
|
||||
|
@ -80,13 +81,6 @@ export function hydrateDeRegMChunks(data, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @return captcha solution
|
||||
*/
|
||||
export function hydrateCaptchaSolution(data) {
|
||||
return data.toString('utf8', 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* @return chunk id and array of pixel offset and color
|
||||
*/
|
||||
|
@ -225,6 +219,10 @@ export function dehydratePixelReturn(
|
|||
return buffer;
|
||||
}
|
||||
|
||||
export function dehydrateCaptchaReturn(retCode) {
|
||||
return Buffer.from([CAPTCHA_RETURN_OP, retCode]);
|
||||
}
|
||||
|
||||
/*
|
||||
* @param canvasId
|
||||
* @param Array with chunk coordinates
|
||||
|
|
|
@ -250,15 +250,6 @@ export function receiveBigChunkFailure(center, error) {
|
|||
};
|
||||
}
|
||||
|
||||
export function receiveCoolDown(
|
||||
wait,
|
||||
) {
|
||||
return {
|
||||
type: 'REC_COOLDOWN',
|
||||
wait,
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveMe(
|
||||
me,
|
||||
) {
|
||||
|
@ -296,13 +287,6 @@ export function receiveStats(
|
|||
};
|
||||
}
|
||||
|
||||
export function receiveOnline(online) {
|
||||
return {
|
||||
type: 'REC_ONLINE',
|
||||
online,
|
||||
};
|
||||
}
|
||||
|
||||
export function sendChatMessage(
|
||||
text,
|
||||
channel,
|
||||
|
@ -314,27 +298,6 @@ export function sendChatMessage(
|
|||
};
|
||||
}
|
||||
|
||||
export function receiveChatMessage(
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channel,
|
||||
user,
|
||||
isPing,
|
||||
isRead,
|
||||
) {
|
||||
return {
|
||||
type: 's/REC_CHAT_MESSAGE',
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channel,
|
||||
user,
|
||||
isPing,
|
||||
isRead,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* check socket/packets/PixelReturn.js for args
|
||||
*/
|
||||
|
@ -445,13 +408,6 @@ export function initTimer() {
|
|||
};
|
||||
}
|
||||
|
||||
export function addChatChannel(channel) {
|
||||
return {
|
||||
type: 's/ADD_CHAT_CHANNEL',
|
||||
channel,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockUser(userId, userName) {
|
||||
return {
|
||||
type: 's/BLOCK_USER',
|
||||
|
@ -482,13 +438,6 @@ export function privatize(priv) {
|
|||
};
|
||||
}
|
||||
|
||||
export function removeChatChannel(cid) {
|
||||
return {
|
||||
type: 's/REMOVE_CHAT_CHANNEL',
|
||||
cid,
|
||||
};
|
||||
}
|
||||
|
||||
export function muteChatChannel(cid) {
|
||||
return {
|
||||
type: 's/MUTE_CHAT_CHANNEL',
|
||||
|
|
74
src/store/actions/socket.js
Normal file
74
src/store/actions/socket.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* actions that are fired when received by the websocket
|
||||
*/
|
||||
export function socketClose() {
|
||||
return {
|
||||
type: 'w/CLOSE',
|
||||
};
|
||||
}
|
||||
|
||||
export function socketOpen() {
|
||||
return {
|
||||
type: 'w/OPEN',
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveChatMessage(
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channel,
|
||||
user,
|
||||
) {
|
||||
return (dispatch, getState) => {
|
||||
channel = Number(channel);
|
||||
const state = getState();
|
||||
const isRead = state.windows.showWindows
|
||||
// eslint-disable-next-line max-len
|
||||
&& state.windows.windows.find((win) => win.windowType === 'CHAT' && !win.hidden)
|
||||
// eslint-disable-next-line max-len
|
||||
&& Object.values(state.windows.args).find((args) => args.chatChannel === channel);
|
||||
|
||||
// TODO ping doesn't work since update
|
||||
// const { nameRegExp } = state.user;
|
||||
// const isPing = (nameRegExp && text.match(nameRegExp));
|
||||
dispatch({
|
||||
type: 's/REC_CHAT_MESSAGE',
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channel,
|
||||
user,
|
||||
isPing: false,
|
||||
isRead: !!isRead,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveCoolDown(wait) {
|
||||
return {
|
||||
type: 'REC_COOLDOWN',
|
||||
wait,
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveOnline(online) {
|
||||
return {
|
||||
type: 'REC_ONLINE',
|
||||
online,
|
||||
};
|
||||
}
|
||||
|
||||
export function addChatChannel(channel) {
|
||||
return {
|
||||
type: 's/ADD_CHAT_CHANNEL',
|
||||
channel,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeChatChannel(cid) {
|
||||
return {
|
||||
type: 's/REMOVE_CHAT_CHANNEL',
|
||||
cid,
|
||||
};
|
||||
}
|
|
@ -11,9 +11,7 @@ import {
|
|||
requestChatMessages,
|
||||
requestMe,
|
||||
} from './fetch';
|
||||
|
||||
import {
|
||||
addChatChannel,
|
||||
pAlert,
|
||||
receiveStats,
|
||||
receiveMe,
|
||||
|
@ -21,8 +19,11 @@ import {
|
|||
unblockUser,
|
||||
blockingDm,
|
||||
privatize,
|
||||
removeChatChannel,
|
||||
} from './index';
|
||||
import {
|
||||
addChatChannel,
|
||||
removeChatChannel,
|
||||
} from './socket';
|
||||
|
||||
function setApiFetching(fetching) {
|
||||
return {
|
||||
|
|
|
@ -48,10 +48,7 @@ export default (store) => (next) => (action) => {
|
|||
const ret = next(action);
|
||||
const state = store.getState();
|
||||
const { canvasId } = state.canvas;
|
||||
if (prevState.canvas.canvasId === canvasId) {
|
||||
// TODO see if this is the case anywhere
|
||||
console.log('Not triggering change canvas');
|
||||
} else {
|
||||
if (prevState.canvas.canvasId !== canvasId) {
|
||||
SocketClient.setCanvas(canvasId);
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
|
||||
/*
|
||||
export type CanvasState = {
|
||||
canvasId: number,
|
||||
canvasId: string,
|
||||
canvasIdent: string,
|
||||
selectedColor: number,
|
||||
is3D: boolean,
|
||||
|
|
Loading…
Reference in New Issue
Block a user