diff --git a/src/client.js b/src/client.js index f6409bf..ef01ecb 100644 --- a/src/client.js +++ b/src/client.js @@ -114,7 +114,7 @@ persistStore(store, {}, () => { store.dispatch(initTimer()); store.dispatch(fetchMe()); - SocketClient.connect(); + SocketClient.initialize(store); }); (function load() { diff --git a/src/components/GlobalCaptcha.jsx b/src/components/GlobalCaptcha.jsx index 65e099d..f902864 100644 --- a/src/components/GlobalCaptcha.jsx +++ b/src/components/GlobalCaptcha.jsx @@ -23,7 +23,7 @@ const GlobalCaptcha = ({ close }) => {
{ e.preventDefault(); - const text = e.target.captcha.value; + const text = e.target.captcha.value.slice(0, 6); if (!text || text.length < 4) { return; } diff --git a/src/core/Void.js b/src/core/Void.js index cb537f7..2f6128c 100644 --- a/src/core/Void.js +++ b/src/core/Void.js @@ -6,7 +6,9 @@ * */ import socketEvents from '../socket/socketEvents'; -import PixelUpdate from '../socket/packets/PixelUpdateServer'; +import { + hydratePixelUpdate, +} from '../socket/packets/server'; import { setPixelByOffset } from './setPixel'; import { TILE_SIZE } from './constants'; import { CANVAS_ID } from '../data/redis/Event'; @@ -177,7 +179,7 @@ class Void { i: pi, j: pj, pixels, - } = PixelUpdate.hydrate(buffer); + } = hydratePixelUpdate(buffer); const { i, j } = this; // 3x3 chunk area (this is hardcoded on multiple places) if (pi >= i - 1 && pi <= i + 1 && pj >= j - 1 && pj <= j + 1) { diff --git a/src/popup.js b/src/popup.js index d95c7c1..847f028 100644 --- a/src/popup.js +++ b/src/popup.js @@ -69,7 +69,7 @@ persistStore(store, {}, () => { if (!parentExists()) { store.dispatch(fetchMe()); - SocketClient.connect(); + SocketClient.initialize(store); } }); diff --git a/src/socket/MessageBroker.js b/src/socket/MessageBroker.js index 46a2e83..67a7a8f 100644 --- a/src/socket/MessageBroker.js +++ b/src/socket/MessageBroker.js @@ -8,10 +8,20 @@ import { SHARD_NAME } from '../core/config'; import SocketEvents from './SockEvents'; -import OnlineCounter from './packets/OnlineCounter'; -import PixelUpdate from './packets/PixelUpdateServer'; -import PixelUpdateMB from './packets/PixelUpdateMB'; -import ChunkUpdate from './packets/ChunkUpdate'; +import { + ONLINE_COUNTER_OP, + PIXEL_UPDATE_MB_OP, + CHUNK_UPDATE_MB_OP, +} from './packets/op'; +import { + hydrateOnlineCounter, + hydratePixelUpdateMB, + hydrateChunkUpdateMB, + dehydratePixelUpdate, + dehydrateOnlineCounter, + dehydratePixelUpdateMB, + dehydrateChunkUpdateMB, +} from './packets/server'; import { pubsub } from '../data/redis/client'; import { combineObjects } from '../core/utils'; @@ -253,25 +263,25 @@ class MessageBroker extends SocketEvents { try { const opcode = buffer[0]; switch (opcode) { - case PixelUpdateMB.OP_CODE: { - const puData = PixelUpdateMB.hydrate(buffer); + case PIXEL_UPDATE_MB_OP: { + const puData = hydratePixelUpdateMB(buffer); super.emit('pixelUpdate', ...puData); const chunkId = puData[1]; const chunk = [chunkId >> 8, chunkId & 0xFF]; super.emit('chunkUpdate', puData[0], chunk); break; } - case ChunkUpdate.OP_CODE: { - super.emit('chunkUpdate', ...ChunkUpdate.hydrate(buffer)); + case CHUNK_UPDATE_MB_OP: { + super.emit('chunkUpdate', ...hydrateChunkUpdateMB(buffer)); break; } - case OnlineCounter.OP_CODE: { + case ONLINE_COUNTER_OP: { const data = new DataView( buffer.buffer, buffer.byteOffset, buffer.length, ); - const cnt = OnlineCounter.hydrate(data); + const cnt = hydrateOnlineCounter(data); this.updateShardOnlineCounter(shard, cnt); break; } @@ -324,9 +334,9 @@ class MessageBroker extends SocketEvents { const j = chunkId & 0xFF; this.publisher.publish( this.thisShard, - PixelUpdateMB.dehydrate(canvasId, i, j, pixels), + dehydratePixelUpdateMB(canvasId, i, j, pixels), ); - const buffer = PixelUpdate.dehydrate(i, j, pixels); + const buffer = dehydratePixelUpdate(i, j, pixels); super.emit('pixelUpdate', canvasId, chunkId, buffer); super.emit('chunkUpdate', canvasId, [i, j]); } @@ -353,18 +363,18 @@ class MessageBroker extends SocketEvents { ) { this.publisher.publish( this.thisShard, - ChunkUpdate.dehydrate(canvasId, chunk), + dehydrateChunkUpdateMB(canvasId, chunk), ); super.emit('chunkUpdate', canvasId, chunk); } broadcastOnlineCounter(online) { this.updateShardOnlineCounter(this.thisShard, online); - let buffer = OnlineCounter.dehydrate(online); // send our online counter to other shards + let buffer = dehydrateOnlineCounter(online); this.publisher.publish(this.thisShard, buffer); // send total counter to our players - buffer = OnlineCounter.dehydrate(this.onlineCounter); + buffer = dehydrateOnlineCounter(this.onlineCounter); super.emit('onlineCounter', buffer); } diff --git a/src/socket/SockEvents.js b/src/socket/SockEvents.js index 919da47..1eca518 100644 --- a/src/socket/SockEvents.js +++ b/src/socket/SockEvents.js @@ -3,9 +3,10 @@ */ import EventEmitter from 'events'; -import OnlineCounter from './packets/OnlineCounter'; -import PixelUpdate from './packets/PixelUpdateServer'; - +import { + dehydrateOnlineCounter, + dehydratePixelUpdate, +} from './packets/server'; class SocketEvents extends EventEmitter { constructor() { @@ -97,7 +98,7 @@ class SocketEvents extends EventEmitter { ) { const i = chunkId >> 8; const j = chunkId & 0xFF; - const buffer = PixelUpdate.dehydrate(i, j, pixels); + const buffer = dehydratePixelUpdate(i, j, pixels); this.emit('pixelUpdate', canvasId, chunkId, buffer); this.emit('chunkUpdate', canvasId, [i, j]); } @@ -254,7 +255,7 @@ class SocketEvents extends EventEmitter { */ broadcastOnlineCounter(online) { this.onlineCounter = online; - const buffer = OnlineCounter.dehydrate(online); + const buffer = dehydrateOnlineCounter(online); this.emit('onlineCounter', buffer); } } diff --git a/src/socket/SocketClient.js b/src/socket/SocketClient.js index 25f4a81..f57ec27 100644 --- a/src/socket/SocketClient.js +++ b/src/socket/SocketClient.js @@ -1,19 +1,28 @@ - // allow the websocket to be noisy on the console /* eslint-disable no-console */ import EventEmitter from 'events'; -import CoolDownPacket from './packets/CoolDownPacket'; -import PixelUpdate from './packets/PixelUpdateClient'; -import PixelReturn from './packets/PixelReturn'; -import OnlineCounter from './packets/OnlineCounter'; -import RegisterCanvas from './packets/RegisterCanvas'; -import RegisterChunk from './packets/RegisterChunk'; -import RegisterMultipleChunks from './packets/RegisterMultipleChunks'; -import DeRegisterChunk from './packets/DeRegisterChunk'; -import ChangedMe from './packets/ChangedMe'; -import Ping from './packets/Ping'; +import { + hydratePixelUpdate, + hydratePixelReturn, + hydrateOnlineCounter, + hydrateCoolDown, + dehydrateRegCanvas, + dehydrateRegChunk, + dehydrateRegMChunks, + dehydrateDeRegChunk, + dehydrateCaptchaSolution, + dehydratePixelUpdate, + dehydratePing, +} from './packets/client'; +import { + PIXEL_UPDATE_OP, + PIXEL_RETURN_OP, + ONLINE_COUNTER_OP, + COOLDOWN_OP, + CHANGE_ME_OP, +} from './packets/op'; import { shardHost } from '../store/actions/fetch'; const chunks = []; @@ -22,8 +31,8 @@ class SocketClient extends EventEmitter { constructor() { super(); console.log('Creating WebSocketClient'); + this.store = null; this.ws = null; - this.canvasId = 0; this.channelId = 0; /* * properties set in connect and open: @@ -38,6 +47,11 @@ class SocketClient extends EventEmitter { setInterval(this.checkHealth, 2000); } + initialize(store) { + this.store = store; + return this.connect(); + } + async connect() { this.readyState = WebSocket.CONNECTING; if (this.ws) { @@ -70,7 +84,7 @@ class SocketClient extends EventEmitter { } if (now - 23000 > this.timeLastSent) { // make sure we send something at least all 25s - this.send(Ping.dehydrate()); + this.send(dehydratePing()); this.timeLastSent = now; } } @@ -110,31 +124,28 @@ class SocketClient extends EventEmitter { this.emit('open', {}); this.readyState = WebSocket.OPEN; - this.send(RegisterCanvas.dehydrate(this.canvasId)); + this.send(dehydrateRegCanvas( + this.store.getState().canvas, + )); console.log(`Register ${chunks.length} chunks`); - this.send(RegisterMultipleChunks.dehydrate(chunks)); + this.send(dehydrateRegMChunks(chunks)); this.processMsgQueue(); } setCanvas(canvasId) { - /* canvasId can be string or integer, thanks to - * JSON not allowing integer keys - */ - // eslint-disable-next-line eqeqeq - if (this.canvasId == canvasId || canvasId === null) { + if (canvasId === null) { return; } console.log('Notify websocket server that we changed canvas'); - this.canvasId = canvasId; chunks.length = 0; - this.send(RegisterCanvas.dehydrate(this.canvasId)); + this.send(dehydrateRegCanvas(canvasId)); } registerChunk(cell) { const [i, j] = cell; const chunkid = (i << 8) | j; chunks.push(chunkid); - const buffer = RegisterChunk.dehydrate(chunkid); + const buffer = dehydrateRegChunk(chunkid); if (this.readyState === WebSocket.OPEN) { this.send(buffer); } @@ -143,7 +154,7 @@ class SocketClient extends EventEmitter { deRegisterChunk(cell) { const [i, j] = cell; const chunkid = (i << 8) | j; - const buffer = DeRegisterChunk.dehydrate(chunkid); + const buffer = dehydrateDeRegChunk(chunkid); if (this.readyState === WebSocket.OPEN) { this.send(buffer); } @@ -151,6 +162,12 @@ class SocketClient extends EventEmitter { if (~pos) chunks.splice(pos, 1); } + /* + sendCaptchaSolution(solution) { + const buffer = dehydrateCaptchaSolution(solution); + } + */ + /* * Send pixel request * @param i, j chunk coordinates @@ -160,7 +177,7 @@ class SocketClient extends EventEmitter { i, j, pixels, ) { - const buffer = PixelUpdate.dehydrate(i, j, pixels); + const buffer = dehydratePixelUpdate(i, j, pixels); this.sendWhenReady(buffer); } @@ -196,7 +213,6 @@ class SocketClient extends EventEmitter { name, text, country, Number(channelId), userId); return; } - case 2: { // signal const [signal, args] = data; @@ -217,19 +233,19 @@ class SocketClient extends EventEmitter { this.timeLastPing = Date.now(); switch (opcode) { - case PixelUpdate.OP_CODE: - this.emit('pixelUpdate', PixelUpdate.hydrate(data)); + case PIXEL_UPDATE_OP: + this.emit('pixelUpdate', hydratePixelUpdate(data)); break; - case PixelReturn.OP_CODE: - this.emit('pixelReturn', PixelReturn.hydrate(data)); + case PIXEL_RETURN_OP: + this.emit('pixelReturn', hydratePixelReturn(data)); break; - case OnlineCounter.OP_CODE: - this.emit('onlineCounter', OnlineCounter.hydrate(data)); + case ONLINE_COUNTER_OP: + this.emit('onlineCounter', hydrateOnlineCounter(data)); break; - case CoolDownPacket.OP_CODE: - this.emit('cooldownPacket', CoolDownPacket.hydrate(data)); + case COOLDOWN_OP: + this.emit('cooldownPacket', hydrateCoolDown(data)); break; - case ChangedMe.OP_CODE: + case CHANGE_ME_OP: console.log('Websocket requested api/me reload'); this.emit('changedMe'); this.reconnect(); diff --git a/src/socket/SocketServer.js b/src/socket/SocketServer.js index 7bc765a..2fbee83 100644 --- a/src/socket/SocketServer.js +++ b/src/socket/SocketServer.js @@ -7,18 +7,28 @@ import logger from '../core/logger'; import canvases from '../core/canvases'; import Counter from '../utils/Counter'; import { getIPFromRequest, getHostFromRequest } from '../utils/ip'; - -import CoolDownPacket from './packets/CoolDownPacket'; -import PixelUpdate from './packets/PixelUpdateServer'; -import PixelReturn from './packets/PixelReturn'; -import RegisterCanvas from './packets/RegisterCanvas'; -import RegisterChunk from './packets/RegisterChunk'; -import RegisterMultipleChunks from './packets/RegisterMultipleChunks'; -import DeRegisterChunk from './packets/DeRegisterChunk'; -import DeRegisterMultipleChunks from './packets/DeRegisterMultipleChunks'; -import ChangedMe from './packets/ChangedMe'; -import OnlineCounter from './packets/OnlineCounter'; - +import { + REG_CANVAS_OP, + PIXEL_UPDATE_OP, + REG_CHUNK_OP, + REG_MCHUNKS_OP, + DEREG_CHUNK_OP, + DEREG_MCHUNKS_OP, +} from './packets/op'; +import { + hydrateRegCanvas, + hydrateRegChunk, + hydrateDeRegChunk, + hydrateRegMChunks, + hydrateDeRegMChunks, + hydrateCaptchaSolution, + hydratePixelUpdate, + dehydrateChangeMe, + dehydrateOnlineCounter, + dehydratePixelUpdate, + dehydrateCoolDown, + dehydratePixelReturn, +} from './packets/server'; import socketEvents from './socketEvents'; import chatProvider, { ChatProvider } from '../core/ChatProvider'; import authenticateClient from './authenticateClient'; @@ -88,7 +98,7 @@ class SocketServer { const { ip } = user; - ws.send(OnlineCounter.dehydrate(socketEvents.onlineCounter)); + ws.send(dehydrateOnlineCounter(socketEvents.onlineCounter)); ws.on('error', (e) => { logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`); @@ -341,7 +351,7 @@ class SocketServer { if (ws.name === name) { await ws.user.reload(); ws.name = ws.user.getName(); - const buffer = ChangedMe.dehydrate(); + const buffer = dehydrateChangeMe(); ws.send(buffer); } }); @@ -474,7 +484,7 @@ class SocketServer { try { const opcode = buffer[0]; switch (opcode) { - case PixelUpdate.OP_CODE: { + case PIXEL_UPDATE_OP: { const { canvasId, user } = ws; const { ip } = user; @@ -499,7 +509,7 @@ class SocketServer { const { i, j, pixels, - } = PixelUpdate.hydrate(buffer); + } = hydratePixelUpdate(buffer); const { wait, coolDown, @@ -522,7 +532,7 @@ class SocketServer { } } - ws.send(PixelReturn.dehydrate( + ws.send(dehydratePixelReturn( retCode, wait, coolDown, @@ -531,42 +541,38 @@ class SocketServer { )); break; } - case RegisterCanvas.OP_CODE: { - const canvasId = RegisterCanvas.hydrate(buffer); + case REG_CANVAS_OP: { + const canvasId = hydrateRegCanvas(buffer); if (!canvases[canvasId]) return; if (ws.canvasId !== null && ws.canvasId !== canvasId) { this.deleteAllChunks(ws); } ws.canvasId = canvasId; const wait = await ws.user.getWait(canvasId); - ws.send(CoolDownPacket.dehydrate(wait)); + ws.send(dehydrateCoolDown(wait)); break; } - case RegisterChunk.OP_CODE: { - const chunkid = RegisterChunk.hydrate(buffer); + case REG_CHUNK_OP: { + const chunkid = hydrateRegChunk(buffer); this.pushChunk(chunkid, ws); break; } - case RegisterMultipleChunks.OP_CODE: { + case REG_MCHUNKS_OP: { this.deleteAllChunks(ws); - let posu = 2; - while (posu < buffer.length) { - const chunkid = buffer[posu++] | buffer[posu++] << 8; + hydrateRegMChunks(buffer, (chunkid) => { this.pushChunk(chunkid, ws); - } + }); break; } - case DeRegisterChunk.OP_CODE: { - const chunkidn = DeRegisterChunk.hydrate(buffer); - this.deleteChunk(chunkidn, ws); + case DEREG_CHUNK_OP: { + const chunkid = hydrateDeRegChunk(buffer); + this.deleteChunk(chunkid, ws); break; } - case DeRegisterMultipleChunks.OP_CODE: { - let posl = 2; - while (posl < buffer.length) { - const chunkid = buffer[posl++] | buffer[posl++] << 8; + case DEREG_MCHUNKS_OP: { + hydrateDeRegMChunks(buffer, (chunkid) => { this.deleteChunk(chunkid, ws); - } + }); break; } default: diff --git a/src/socket/packets/ChangedMe.js b/src/socket/packets/ChangedMe.js deleted file mode 100644 index 967fe08..0000000 --- a/src/socket/packets/ChangedMe.js +++ /dev/null @@ -1,10 +0,0 @@ -const OP_CODE = 0xA6; - -export default { - OP_CODE, - - dehydrate() { - // Server (sender) - return new Uint8Array([OP_CODE]).buffer; - }, -}; diff --git a/src/socket/packets/ChunkUpdate.js b/src/socket/packets/ChunkUpdate.js deleted file mode 100644 index 6e34e76..0000000 --- a/src/socket/packets/ChunkUpdate.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * notify that chunk changed - * (not sent over websocket, server only) - */ - -const OP_CODE = 0xC4; - -export default { - OP_CODE, - /* - * @return canvasId, [i, j] - */ - hydrate(data) { - const canvasId = data[1]; - const i = data.readUInt8(2); - const j = data.readUInt8(3); - return [canvasId, [i, j]]; - }, - /* - * @param canvasId, - * chunkid id consisting of chunk coordinates - */ - dehydrate(canvasId, [i, j]) { - return Buffer.from([ - OP_CODE, - canvasId, - i, - j, - ]); - }, -}; diff --git a/src/socket/packets/CoolDownPacket.js b/src/socket/packets/CoolDownPacket.js deleted file mode 100644 index eba390e..0000000 --- a/src/socket/packets/CoolDownPacket.js +++ /dev/null @@ -1,16 +0,0 @@ -const OP_CODE = 0xC2; - -export default { - OP_CODE, - hydrate(data) { - // client (receiver) - return data.getUint32(1); - }, - dehydrate(wait) { - // Server (sender) - const buffer = Buffer.allocUnsafe(1 + 4); - buffer.writeUInt8(OP_CODE, 0); - buffer.writeUInt32BE(wait, 1); - return buffer; - }, -}; diff --git a/src/socket/packets/DeRegisterChunk.js b/src/socket/packets/DeRegisterChunk.js deleted file mode 100644 index cae1ffa..0000000 --- a/src/socket/packets/DeRegisterChunk.js +++ /dev/null @@ -1,18 +0,0 @@ -const OP_CODE = 0xA2; - -export default { - OP_CODE, - hydrate(data) { - // SERVER (Receiver) - const i = data[1] << 8 | data[2]; - return i; - }, - dehydrate(chunkid) { - // CLIENT (Sender) - const buffer = new ArrayBuffer(1 + 2); - const view = new DataView(buffer); - view.setInt8(0, OP_CODE); - view.setInt16(1, chunkid); - return buffer; - }, -}; diff --git a/src/socket/packets/DeRegisterMultipleChunks.js b/src/socket/packets/DeRegisterMultipleChunks.js deleted file mode 100644 index 02e5eb7..0000000 --- a/src/socket/packets/DeRegisterMultipleChunks.js +++ /dev/null @@ -1,20 +0,0 @@ -const OP_CODE = 0xA4; - -export default { - OP_CODE, - /* - * @param chunks Array of chunks - */ - dehydrate(chunks) { - // CLIENT (Sender) - const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2); - const view = new Uint16Array(buffer); - // this will result into a double first byte, but still better than - // shifting 16bit integers around later - view[0] = OP_CODE; - for (let cnt = 0; cnt < chunks.length; cnt += 1) { - view[cnt + 1] = chunks[cnt]; - } - return buffer; - }, -}; diff --git a/src/socket/packets/OnlineCounter.js b/src/socket/packets/OnlineCounter.js deleted file mode 100644 index 2f6cbf1..0000000 --- a/src/socket/packets/OnlineCounter.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * Numbers of online players per canvas - * - */ - -const OP_CODE = 0xA7; - -export default { - OP_CODE, - // CLIENT (receiver) - /* - * { - * total: totalOnline, - * canvasId: online, - * .... - * } - */ - hydrate(data) { - const online = {}; - online.total = data.getUint16(1); - let off = data.byteLength; - while (off > 3) { - const onlineUsers = data.getUint16(off -= 2); - const canvas = data.getUint8(off -= 1); - online[canvas] = onlineUsers; - } - return online; - }, - - dehydrate(online) { - // SERVER (sender) - if (!process.env.BROWSER) { - const canvasIds = Object.keys(online).filter((id) => id !== 'total'); - - const buffer = Buffer.allocUnsafe(3 + canvasIds.length * (1 + 2)); - buffer.writeUInt8(OP_CODE, 0); - buffer.writeUInt16BE(online.total, 1); - let cnt = 1; - for (let p = 0; p < canvasIds.length; p += 1) { - const canvasId = canvasIds[p]; - const onlineUsers = online[canvasId]; - buffer.writeUInt8(Number(canvasId), cnt += 2); - buffer.writeUInt16BE(onlineUsers, cnt += 1); - } - - return buffer; - } - return 0; - }, -}; diff --git a/src/socket/packets/Ping.js b/src/socket/packets/Ping.js deleted file mode 100644 index c80f6be..0000000 --- a/src/socket/packets/Ping.js +++ /dev/null @@ -1,10 +0,0 @@ -const OP_CODE = 0xB0; - -export default { - OP_CODE, - - dehydrate() { - // Client (sender) - return new Uint8Array([OP_CODE]).buffer; - }, -}; diff --git a/src/socket/packets/PixelReturn.js b/src/socket/packets/PixelReturn.js deleted file mode 100644 index 9306f44..0000000 --- a/src/socket/packets/PixelReturn.js +++ /dev/null @@ -1,38 +0,0 @@ -const OP_CODE = 0xC3; - -export default { - OP_CODE, - hydrate(data) { - // Client (receiver) - const retCode = data.getUint8(1); - const wait = data.getUint32(2); - const coolDownSeconds = data.getInt16(6); - const pxlCnt = data.getUint8(8); - const rankedPxlCnt = data.getUint8(9); - return { - retCode, - wait, - coolDownSeconds, - pxlCnt, - rankedPxlCnt, - }; - }, - dehydrate( - retCode, - wait, - coolDown, - pxlCnt, - rankedPxlCnt, - ) { - // Server (sender) - const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 2 + 1 + 1); - buffer.writeUInt8(OP_CODE, 0); - buffer.writeUInt8(retCode, 1); - buffer.writeUInt32BE(wait, 2); - const coolDownSeconds = Math.round(coolDown / 1000); - buffer.writeInt16BE(coolDownSeconds, 6); - buffer.writeUInt8(pxlCnt, 8); - buffer.writeUInt8(rankedPxlCnt, 9); - return buffer; - }, -}; diff --git a/src/socket/packets/PixelUpdateClient.js b/src/socket/packets/PixelUpdateClient.js deleted file mode 100644 index b5d10d5..0000000 --- a/src/socket/packets/PixelUpdateClient.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Packet for sending and receiving pixels per chunk - * Multiple pixels can be sent at once - * Client side - * - */ - -const OP_CODE = 0xC1; - -export default { - OP_CODE, - /* - * @param data DataVies - */ - hydrate(data) { - /* - * chunk coordinates - */ - const i = data.getUint8(1); - const j = data.getUint8(2); - /* - * offset and color of every pixel - * 3 bytes offset - * 1 byte color - */ - const pixels = []; - let off = data.byteLength; - while (off > 3) { - const color = data.getUint8(off -= 1); - const offsetL = data.getUint16(off -= 2); - const offsetH = data.getUint8(off -= 1) << 16; - pixels.push([offsetH | offsetL, color]); - } - return { - i, j, pixels, - }; - }, - - dehydrate(i, j, pixels) { - const buffer = new ArrayBuffer(1 + 1 + 1 + pixels.length * 4); - const view = new DataView(buffer); - view.setUint8(0, OP_CODE); - /* - * chunk coordinates - */ - view.setUint8(1, i); - view.setUint8(2, j); - /* - * offset and color of every pixel - * 3 bytes offset - * 1 byte color - */ - let cnt = 2; - let p = pixels.length; - while (p) { - p -= 1; - const [offset, color] = pixels[p]; - view.setUint8(cnt += 1, offset >>> 16); - view.setUint16(cnt += 1, offset & 0x00FFFF); - view.setUint8(cnt += 2, color); - } - - return buffer; - }, - -}; diff --git a/src/socket/packets/PixelUpdateMB.js b/src/socket/packets/PixelUpdateMB.js deleted file mode 100644 index 5a1245a..0000000 --- a/src/socket/packets/PixelUpdateMB.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Packet for sending and receiving pixels over Message Broker between shards - * Multiple pixels can be sent at once - * - */ - -const OP_CODE = 0xC1; - -export default { - OP_CODE, - /* - * returns info and PixelUpdate package to send to clients - */ - hydrate(data) { - const canvasId = data[1]; - data.writeUInt8(OP_CODE, 1); - const chunkId = data.readUInt16BE(2); - const pixelUpdate = Buffer.from( - data.buffer, - data.byteOffset + 1, - data.length - 1, - ); - return [ - canvasId, - chunkId, - pixelUpdate, - ]; - }, - - /* - * @param canvasId - * @param chunkId id consisting of chunk coordinates - * @param pixels Buffer with offset and color of one or more pixels - */ - dehydrate(canvasId, i, j, pixels) { - const index = new Uint8Array([ - OP_CODE, - canvasId, - i, - j, - ]); - return Buffer.concat([index, pixels]); - }, -}; diff --git a/src/socket/packets/PixelUpdateServer.js b/src/socket/packets/PixelUpdateServer.js deleted file mode 100644 index 56f312a..0000000 --- a/src/socket/packets/PixelUpdateServer.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Packet for sending and receiving pixels per chunk - * Multiple pixels can be sent at once - * Server side. - * - * */ - -const OP_CODE = 0xC1; - -export default { - OP_CODE, - hydrate(data) { - /* - * chunk coordinates - */ - const i = data.readUInt8(1); - const j = data.readUInt8(2); - /* - * offset and color of every pixel - * 3 bytes offset - * 1 byte color - */ - const pixels = []; - let off = data.length; - /* - * limit the max amount of pixels that can be - * receive to 500 - */ - let pxlcnt = 0; - while (off > 3 && pxlcnt < 500) { - const color = data.readUInt8(off -= 1); - const offsetL = data.readUInt16BE(off -= 2); - const offsetH = data.readUInt8(off -= 1) << 16; - pixels.push([offsetH | offsetL, color]); - pxlcnt += 1; - } - return { - i, j, pixels, - }; - }, - - /* - * @param chunkId id consisting of chunk coordinates - * @param pixels Buffer with offset and color of one or more pixels - */ - dehydrate(i, j, pixels) { - const index = new Uint8Array([OP_CODE, i, j]); - return Buffer.concat([index, pixels]); - }, -}; diff --git a/src/socket/packets/README.md b/src/socket/packets/README.md index d758a2a..f4b627e 100644 --- a/src/socket/packets/README.md +++ b/src/socket/packets/README.md @@ -1,6 +1,4 @@ # Binary Websocket Packages -Note that the node Server receives in [Buffer](https://nodejs.org/api/buffer.html), while the client receives [DataViews](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) and sends ArrayBuffers. -Therefor the server can't share the same code with the client for hydrate / dehydrate. -Most packages are unidirectional so hydrate is for either client or server and dehydrate for the other one. -Bidrectional packages have two files, one for Client, another one for Server. +Note that the node Server receives and sends in [Buffer](https://nodejs.org/api/buffer.html), while the client receives [DataViews](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) and sends ArrayBuffers. +Therefor the server can't share the same code with the client for hydrate / dehydrate and it's split in two files. diff --git a/src/socket/packets/RegisterCanvas.js b/src/socket/packets/RegisterCanvas.js deleted file mode 100644 index a4c2ebb..0000000 --- a/src/socket/packets/RegisterCanvas.js +++ /dev/null @@ -1,18 +0,0 @@ -const OP_CODE = 0xA0; - -export default { - OP_CODE, - hydrate(data) { - // SERVER (Receiver) - const canvasId = data[1]; - return canvasId; - }, - dehydrate(canvasId) { - // CLIENT (Sender) - const buffer = new ArrayBuffer(1 + 1); - const view = new DataView(buffer); - view.setInt8(0, OP_CODE); - view.setInt8(1, Number(canvasId)); - return buffer; - }, -}; diff --git a/src/socket/packets/RegisterChunk.js b/src/socket/packets/RegisterChunk.js deleted file mode 100644 index ed31a30..0000000 --- a/src/socket/packets/RegisterChunk.js +++ /dev/null @@ -1,18 +0,0 @@ -const OP_CODE = 0xA1; - -export default { - OP_CODE, - hydrate(data) { - // SERVER (Receiver) - const i = data[1] << 8 | data[2]; - return i; - }, - dehydrate(chunkid) { - // CLIENT (Sender) - const buffer = new ArrayBuffer(1 + 2); - const view = new DataView(buffer); - view.setInt8(0, OP_CODE); - view.setInt16(1, chunkid); - return buffer; - }, -}; diff --git a/src/socket/packets/RegisterMultipleChunks.js b/src/socket/packets/RegisterMultipleChunks.js deleted file mode 100644 index f2a0b44..0000000 --- a/src/socket/packets/RegisterMultipleChunks.js +++ /dev/null @@ -1,20 +0,0 @@ -const OP_CODE = 0xA3; - -export default { - OP_CODE, - /* - * @param chunks Array of chunks - */ - dehydrate(chunks) { - // CLIENT (Sender) - const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2); - const view = new Uint16Array(buffer); - // this will result into a double first byte, but still better than - // shifting 16bit integers around later - view[0] = OP_CODE; - for (let cnt = 0; cnt < chunks.length; cnt += 1) { - view[cnt + 1] = chunks[cnt]; - } - return buffer; - }, -}; diff --git a/src/socket/packets/client.js b/src/socket/packets/client.js new file mode 100644 index 0000000..0a70484 --- /dev/null +++ b/src/socket/packets/client.js @@ -0,0 +1,196 @@ +/* + * client package hydration + */ +import { + REG_CANVAS_OP, + REG_CHUNK_OP, + DEREG_CHUNK_OP, + REG_MCHUNKS_OP, + DEREG_MCHUNKS_OP, + CAPTCHA_SOLUTION_OP, + PING_OP, + PIXEL_UPDATE_OP, +} from './op'; + +/* +* data in hydrate functions is DataView +*/ + +/* + * @return { + * total: totalOnline, + * canvasId: online, + * .... + * } + */ +export function hydrateOnlineCounter(data) { + const online = {}; + online.total = data.getUint16(1); + let off = data.byteLength; + while (off > 3) { + const onlineUsers = data.getUint16(off -= 2); + const canvas = data.getUint8(off -= 1); + online[canvas] = onlineUsers; + } + return online; +} + +/* + * @return chunk coordinates and array of pixel offset and colors + */ +export function hydratePixelUpdate(data) { + const i = data.getUint8(1); + const j = data.getUint8(2); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + const pixels = []; + let off = data.byteLength; + while (off > 3) { + const color = data.getUint8(off -= 1); + const offsetL = data.getUint16(off -= 2); + const offsetH = data.getUint8(off -= 1) << 16; + pixels.push([offsetH | offsetL, color]); + } + return { + i, j, pixels, + }; +} + +/* + * @return cooldown in ms + */ +export function hydrateCoolDown(data) { + return data.getUint32(1); +} + +/* + * @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); + const pxlCnt = data.getUint8(8); + const rankedPxlCnt = data.getUint8(9); + return { + retCode, + wait, + coolDownSeconds, + pxlCnt, + rankedPxlCnt, + }; +} + +/* + * dehydrate functions return ArrayBuffer object + */ + +/* + * @param canvasId + */ +export function dehydrateRegCanvas(canvasId) { + const buffer = new ArrayBuffer(1 + 1); + const view = new DataView(buffer); + view.setInt8(0, REG_CANVAS_OP); + view.setInt8(1, Number(canvasId)); + return buffer; +} + +/* + * @param chunkid + */ +export function dehydrateRegChunk(chunkid) { + const buffer = new ArrayBuffer(1 + 2); + const view = new DataView(buffer); + view.setInt8(0, REG_CHUNK_OP); + view.setInt16(1, chunkid); + return buffer; +} + +/* + * @param chunkid + */ +export function dehydrateDeRegChunk(chunkid) { + const buffer = new ArrayBuffer(1 + 2); + const view = new DataView(buffer); + view.setInt8(0, DEREG_CHUNK_OP); + view.setInt16(1, chunkid); + return buffer; +} + +/* + * @param chunks Array of chunkIds + */ +export function dehydrateRegMChunks(chunks) { + const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2); + const view = new Uint16Array(buffer); + // this will result into a double first byte, but still better than + // shifting 16bit integers around later + view[0] = REG_MCHUNKS_OP; + for (let cnt = 0; cnt < chunks.length; cnt += 1) { + view[cnt + 1] = chunks[cnt]; + } + return buffer; +} + +/* + * @param chunks Array of chunkIds + */ +export function dehydrateDeRegMChunks(chunks) { + const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2); + const view = new Uint16Array(buffer); + // this will result into a double first byte, but still better than + // shifting 16bit integers around later + view[0] = DEREG_MCHUNKS_OP; + for (let cnt = 0; cnt < chunks.length; cnt += 1) { + view[cnt + 1] = chunks[cnt]; + } + 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; +} + +/* + * @param i, j chunk coordinates + * @param pixels array of offsets and colors of pixels + */ +export function dehydratePixelUpdate(i, j, pixels) { + const buffer = new ArrayBuffer(1 + 1 + 1 + pixels.length * 4); + const view = new DataView(buffer); + view.setUint8(0, PIXEL_UPDATE_OP); + view.setUint8(1, i); + view.setUint8(2, j); + /* + * offset and color of every pixel + * 3 bytes offset + * 1 byte color + */ + let cnt = 2; + let p = pixels.length; + while (p) { + p -= 1; + const [offset, color] = pixels[p]; + view.setUint8(cnt += 1, offset >>> 16); + view.setUint16(cnt += 1, offset & 0x00FFFF); + view.setUint8(cnt += 2, color); + } + return buffer; +} diff --git a/src/socket/packets/op.js b/src/socket/packets/op.js new file mode 100644 index 0000000..263d5ef --- /dev/null +++ b/src/socket/packets/op.js @@ -0,0 +1,21 @@ +/* + * OP CODES + */ + +/* + * we export code so that webpack can directly resolve them + */ +export const REG_CANVAS_OP = 0xA0; +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; diff --git a/src/socket/packets/server.js b/src/socket/packets/server.js new file mode 100644 index 0000000..796516d --- /dev/null +++ b/src/socket/packets/server.js @@ -0,0 +1,239 @@ +/* + * server package hydration + */ +import { + CHANGE_ME_OP, + ONLINE_COUNTER_OP, + PIXEL_UPDATE_OP, + PIXEL_UPDATE_MB_OP, + COOLDOWN_OP, + PIXEL_RETURN_OP, + CHUNK_UPDATE_MB_OP, +} from './op'; + +/* + * data in hydrate function is a nodejs Buffer + */ + +/* +* @return canvasId +*/ +export function hydrateRegCanvas(data) { + const canvasId = data[1]; + return canvasId; +} + +/* + * @return { + * total: totalOnline, + * canvasId: online, + * .... + * } + */ +export function hydrateOnlineCounter(data) { + const online = {}; + online.total = data.readUInt16BE(1); + let off = data.length; + while (off > 3) { + const onlineUsers = data.readUInt16BE(off -= 2); + const canvas = data.readUInt8(off -= 1); + online[canvas] = onlineUsers; + } + return online; +} + +/* +* @return chunkId +*/ +export function hydrateRegChunk(data) { + const i = data[1] << 8 | data[2]; + return i; +} + +/* +* @return chunkId +*/ +export function hydrateDeRegChunk(data) { + const i = data[1] << 8 | data[2]; + return i; +} + +/* +* cb execute with individual chunkids +*/ +export function hydrateRegMChunks(data, cb) { + let posu = 2; + while (posu < data.length) { + const chunkid = data[posu++] | data[posu++] << 8; + cb(chunkid); + } +} + +/* +* cb execute with individual chunkids +*/ +export function hydrateDeRegMChunks(data, cb) { + let posl = 2; + while (posl < data.length) { + const chunkid = data[posl++] | data[posl++] << 8; + cb(chunkid); + } +} + +/* +* @return captcha solution +*/ +export function hydrateCaptchaSolution(data) { + return data.toString('utf8', 1); +} + +/* +* @return chunk id and array of pixel offset and color +*/ +export function hydratePixelUpdate(data) { + const i = data.readUInt8(1); + const j = data.readUInt8(2); + const pixels = []; + let off = data.length; + let pxlcnt = 0; + while (off > 3 && pxlcnt < 500) { + const color = data.readUInt8(off -= 1); + const offsetL = data.readUInt16BE(off -= 2); + const offsetH = data.readUInt8(off -= 1) << 16; + pixels.push([offsetH | offsetL, color]); + pxlcnt += 1; + } + return { + i, j, pixels, + }; +} + +/* +* @returns info and PixelUpdate package to send to clients +*/ +export function hydratePixelUpdateMB(data) { + const canvasId = data[1]; + data.writeUInt8(PIXEL_UPDATE_OP, 1); + const chunkId = data.readUInt16BE(2); + const pixelUpdate = Buffer.from( + data.buffer, + data.byteOffset + 1, + data.length - 1, + ); + return [ + canvasId, + chunkId, + pixelUpdate, + ]; +} + +/* +* @return canvasid and chunk coords +*/ +export function hydrateChunkUpdateMB(data) { + const canvasId = data[1]; + const i = data.readUInt8(2); + const j = data.readUInt8(3); + return [canvasId, [i, j]]; +} + +/* + * dehydrate functions return nodejs Buffer object + */ + +/* + * returns buffer with only OP_CODE + */ +export function dehydrateChangeMe() { + return Buffer.from([CHANGE_ME_OP]); +} + +/* + * @param { + * total: totalOnline, + * canvasId: online, + * .... + * } + */ +export function dehydrateOnlineCounter(online) { + const canvasIds = Object.keys(online).filter((id) => id !== 'total'); + const buffer = Buffer.allocUnsafe(3 + canvasIds.length * (1 + 2)); + buffer.writeUInt8(ONLINE_COUNTER_OP, 0); + buffer.writeUInt16BE(online.total, 1); + let cnt = 1; + for (let p = 0; p < canvasIds.length; p += 1) { + const canvasId = canvasIds[p]; + const onlineUsers = online[canvasId]; + buffer.writeUInt8(Number(canvasId), cnt += 2); + buffer.writeUInt16BE(onlineUsers, cnt += 1); + } + return buffer; +} + +/* + * @param chunkId id consisting of chunk coordinates + * @param pixels Buffer with offset and color of one or more pixels + */ +export function dehydratePixelUpdate(i, j, pixels) { + const index = new Uint8Array([PIXEL_UPDATE_OP, i, j]); + return Buffer.concat([index, pixels]); +} + +/* + * @param canvasId + * @param chunkId id consisting of chunk coordinates + * @param pixels Buffer with offset and color of one or more pixels + */ +export function dehydratePixelUpdateMB(canvasId, i, j, pixels) { + const index = new Uint8Array([ + PIXEL_UPDATE_MB_OP, + canvasId, + i, + j, + ]); + return Buffer.concat([index, pixels]); +} + +/* + * @param wait cooldown in ms + */ +export function dehydrateCoolDown(wait) { + const buffer = Buffer.allocUnsafe(1 + 4); + buffer.writeUInt8(COOLDOWN_OP, 0); + buffer.writeUInt32BE(wait, 1); + return buffer; +} + +/* + * for params see core/draw or ui/placePixel + */ +export function dehydratePixelReturn( + retCode, + wait, + coolDown, + pxlCnt, + rankedPxlCnt, +) { + const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 2 + 1 + 1); + buffer.writeUInt8(PIXEL_RETURN_OP, 0); + buffer.writeUInt8(retCode, 1); + buffer.writeUInt32BE(wait, 2); + const coolDownSeconds = Math.round(coolDown / 1000); + buffer.writeInt16BE(coolDownSeconds, 6); + buffer.writeUInt8(pxlCnt, 8); + buffer.writeUInt8(rankedPxlCnt, 9); + return buffer; +} + +/* + * @param canvasId + * @param Array with chunk coordinates + */ +export function dehydrateChunkUpdateMB(canvasId, [i, j]) { + return Buffer.from([ + CHUNK_UPDATE_MB_OP, + canvasId, + i, + j, + ]); +} diff --git a/src/store/actions/index.js b/src/store/actions/index.js index 969df1e..9879ae5 100644 --- a/src/store/actions/index.js +++ b/src/store/actions/index.js @@ -148,7 +148,7 @@ export function selectColor(color) { export function selectCanvas(canvasId) { return { type: 's/SELECT_CANVAS', - canvasId, + canvasId: String(canvasId), }; } diff --git a/src/store/middleware/socketClientHook.js b/src/store/middleware/socketClientHook.js index 2d046c0..5ff839b 100644 --- a/src/store/middleware/socketClientHook.js +++ b/src/store/middleware/socketClientHook.js @@ -41,26 +41,25 @@ export default (store) => (next) => (action) => { break; } - default: - // nothing - } - - const ret = next(action); - - // executed after reducers - switch (action.type) { case 'RELOAD_URL': case 's/SELECT_CANVAS': case 's/REC_ME': { + const prevState = store.getState(); + const ret = next(action); const state = store.getState(); const { canvasId } = state.canvas; - SocketClient.setCanvas(canvasId); - break; + if (prevState.canvas.canvasId === canvasId) { + // TODO see if this is the case anywhere + console.log('Not triggering change canvas'); + } else { + SocketClient.setCanvas(canvasId); + } + return ret; } default: // nothing } - return ret; + return next(action); }; diff --git a/src/store/middleware/socketClientHookPopUp.js b/src/store/middleware/socketClientHookPopUp.js index 69fa260..88780d4 100644 --- a/src/store/middleware/socketClientHookPopUp.js +++ b/src/store/middleware/socketClientHookPopUp.js @@ -5,10 +5,10 @@ import SocketClient from '../../socket/SocketClient'; -export default () => (next) => (action) => { +export default (store) => (next) => (action) => { if (SocketClient.readyState === WebSocket.CLOSED) { if (action.type === 't/PARENT_CLOSED') { - SocketClient.connect(); + SocketClient.initialize(store); } } else { switch (action.type) {