refactor socket events

send captcha solution via websocket
This commit is contained in:
HF 2022-10-03 12:47:03 +02:00
parent 7a2053fda3
commit e9952134a5
30 changed files with 326 additions and 349 deletions

View File

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

View File

@ -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>:&nbsp;{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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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,
};
}

View File

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

View File

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

View File

@ -17,7 +17,7 @@ import {
/*
export type CanvasState = {
canvasId: number,
canvasId: string,
canvasIdent: string,
selectedColor: number,
is3D: boolean,