change error handling for websocket client

fix eslint errors in Tiles.js
This commit is contained in:
HF 2022-06-25 17:00:27 +02:00
parent 28be86718d
commit e31d27bb35
5 changed files with 105 additions and 72 deletions

View File

@ -80,6 +80,9 @@ function addShrunkenSubtileToTile(
} }
} }
/*
* this was a failed try, it ended up being slow
* and low quality
function addShrunkenIndexedSubtilesToTile( function addShrunkenIndexedSubtilesToTile(
palette, palette,
tileSize, tileSize,
@ -102,9 +105,7 @@ function addShrunkenIndexedSubtilesToTile(
let clr; let clr;
const linePad = (tileSize + 1) * (subtilesInTile - 1); const linePad = (tileSize + 1) * (subtilesInTile - 1);
let amountFullRows = Math.floor(inpTileLength / subtilesInTile); let amountFullRows = Math.floor(inpTileLength / subtilesInTile);
/* // use available data
* use available data
*/
for (let row = 0; row < amountFullRows; row += 1) { for (let row = 0; row < amountFullRows; row += 1) {
channelOffset = (offset + row * tileSize) * 3; channelOffset = (offset + row * tileSize) * 3;
const max = channelOffset + subtileSize * 3; const max = channelOffset + subtileSize * 3;
@ -123,9 +124,7 @@ function addShrunkenIndexedSubtilesToTile(
posB += subtilesInTile; posB += subtilesInTile;
} }
} }
/* // padding the rest
* padding the rest
*/
[tr, tg, tb] = rgb; [tr, tg, tb] = rgb;
if (inpTileLength % subtilesInTile) { if (inpTileLength % subtilesInTile) {
channelOffset = (offset + amountFullRows * tileSize) * 3; channelOffset = (offset + amountFullRows * tileSize) * 3;
@ -158,6 +157,7 @@ function addShrunkenIndexedSubtilesToTile(
} }
} }
} }
*/
/* /*
* @param subtilesInTile how many subtiles are in a tile (per dimension) * @param subtilesInTile how many subtiles are in a tile (per dimension)
@ -297,7 +297,13 @@ export async function createZoomTileFromChunk(
if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) { if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) {
na.forEach((element) => { na.forEach((element) => {
deleteSubtilefromTile(TILE_SIZE, palette, TILE_ZOOM_LEVEL, element, tileRGBBuffer); deleteSubtilefromTile(
TILE_SIZE,
palette,
TILE_ZOOM_LEVEL,
element,
tileRGBBuffer,
);
}); });
const filename = tileFileName(canvasTileFolder, [maxTiledZoom - 1, x, y]); const filename = tileFileName(canvasTileFolder, [maxTiledZoom - 1, x, y]);
@ -357,7 +363,12 @@ export async function createZoomedTile(
} }
try { try {
const chunk = await sharp(chunkfile).removeAlpha().raw().toBuffer(); const chunk = await sharp(chunkfile).removeAlpha().raw().toBuffer();
addShrunkenSubtileToTile(TILE_ZOOM_LEVEL, [dx, dy], chunk, tileRGBBuffer); addShrunkenSubtileToTile(
TILE_ZOOM_LEVEL,
[dx, dy],
chunk,
tileRGBBuffer,
);
} catch (error) { } catch (error) {
console.error( console.error(
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
@ -369,7 +380,13 @@ export async function createZoomedTile(
if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) { if (na.length !== TILE_ZOOM_LEVEL * TILE_ZOOM_LEVEL) {
na.forEach((element) => { na.forEach((element) => {
deleteSubtilefromTile(TILE_SIZE / TILE_ZOOM_LEVEL, palette, TILE_ZOOM_LEVEL, element, tileRGBBuffer); deleteSubtilefromTile(
TILE_SIZE / TILE_ZOOM_LEVEL,
palette,
TILE_ZOOM_LEVEL,
element,
tileRGBBuffer,
);
}); });
const filename = tileFileName(canvasTileFolder, [z, x, y]); const filename = tileFileName(canvasTileFolder, [z, x, y]);

View File

@ -1,5 +1,3 @@
/* @flow
*/
// allow the websocket to be noisy on the console // allow the websocket to be noisy on the console
/* eslint-disable no-console */ /* eslint-disable no-console */
@ -15,36 +13,36 @@ import RegisterChunk from './packets/RegisterChunk';
import RegisterMultipleChunks from './packets/RegisterMultipleChunks'; import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
import DeRegisterChunk from './packets/DeRegisterChunk'; import DeRegisterChunk from './packets/DeRegisterChunk';
import ChangedMe from './packets/ChangedMe'; import ChangedMe from './packets/ChangedMe';
import Ping from './packets/Ping';
const chunks = []; const chunks = [];
class SocketClient extends EventEmitter { class SocketClient extends EventEmitter {
url: string;
ws: WebSocket;
canvasId: number;
channelId: number;
timeConnected: number;
isConnected: number;
isConnecting: boolean;
msgQueue: Array;
constructor() { constructor() {
super(); super();
console.log('Creating WebSocketClient'); console.log('Creating WebSocketClient');
this.isConnecting = false;
this.isConnected = false;
this.ws = null; this.ws = null;
this.canvasId = '0'; this.canvasId = '0';
this.channelId = 0; this.channelId = 0;
/*
* properties set in connect and open:
* this.timeLastConnecting
* this.timeLastPing
* this.timeLastSent
*/
this.readyState = WebSocket.CLOSED;
this.msgQueue = []; this.msgQueue = [];
this.checkHealth = this.checkHealth.bind(this);
setInterval(this.checkHealth, 2000);
} }
async connect() { async connect() {
this.isConnecting = true; this.readyState = WebSocket.CONNECTING;
if (this.ws) { if (this.ws) {
console.log('WebSocket already open, not starting'); console.log('WebSocket already open, not starting');
} }
this.timeConnected = Date.now(); this.timeLastConnecting = Date.now();
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.hostname}${ const url = `${protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : '' window.location.port ? `:${window.location.port}` : ''
@ -54,18 +52,34 @@ class SocketClient extends EventEmitter {
this.ws.onopen = this.onOpen.bind(this); this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this); this.ws.onmessage = this.onMessage.bind(this);
this.ws.onclose = this.onClose.bind(this); this.ws.onclose = this.onClose.bind(this);
this.ws.onerror = this.onError.bind(this); this.ws.onerror = (err) => {
console.error('Socket encountered error, closing socket', err);
};
}
checkHealth() {
if (this.readyState === WebSocket.OPEN) {
const now = Date.now();
if (now - 14000 > this.timeLastPing) {
// server didn't send anything, probably dead
console.log('Server is silent, killing websocket');
this.readyState = WebSocket.CLOSING;
this.ws.close();
}
if (now - 10000 > this.timeLastSent) {
// make sure we send something at least all 12s
this.sendWhenReady(Ping.dehydrate());
}
}
} }
sendWhenReady(msg) { sendWhenReady(msg) {
if (this.isConnected) { this.timeLastSent = Date.now();
if (this.readyState === WebSocket.OPEN) {
this.ws.send(msg); this.ws.send(msg);
} else { } else {
console.log('Tried sending message when websocket was closed!'); console.log('Tried sending message when websocket was closed!');
this.msgQueue.push(msg); this.msgQueue.push(msg);
if (!this.isConnecting) {
this.connect();
}
} }
} }
@ -76,8 +90,11 @@ class SocketClient extends EventEmitter {
} }
onOpen() { onOpen() {
this.isConnecting = false; const now = Date.now();
this.isConnected = true; this.timeLastPing = now;
this.timeLastSent = now;
this.readyState = WebSocket.OPEN;
this.emit('open', {}); this.emit('open', {});
if (this.canvasId !== null) { if (this.canvasId !== null) {
this.ws.send(RegisterCanvas.dehydrate(this.canvasId)); this.ws.send(RegisterCanvas.dehydrate(this.canvasId));
@ -87,11 +104,6 @@ class SocketClient extends EventEmitter {
this.ws.send(RegisterMultipleChunks.dehydrate(chunks)); this.ws.send(RegisterMultipleChunks.dehydrate(chunks));
} }
onError(err) {
console.error('Socket encountered error, closing socket', err);
this.ws.close();
}
setCanvas(canvasId) { setCanvas(canvasId) {
/* canvasId can be string or integer, thanks to /* canvasId can be string or integer, thanks to
* JSON not allowing integer keys * JSON not allowing integer keys
@ -111,14 +123,18 @@ class SocketClient extends EventEmitter {
const chunkid = (i << 8) | j; const chunkid = (i << 8) | j;
chunks.push(chunkid); chunks.push(chunkid);
const buffer = RegisterChunk.dehydrate(chunkid); const buffer = RegisterChunk.dehydrate(chunkid);
if (this.isConnected) this.ws.send(buffer); if (this.readyState === WebSocket.OPEN) {
this.ws.send(buffer);
}
} }
deRegisterChunk(cell) { deRegisterChunk(cell) {
const [i, j] = cell; const [i, j] = cell;
const chunkid = (i << 8) | j; const chunkid = (i << 8) | j;
const buffer = DeRegisterChunk.dehydrate(chunkid); const buffer = DeRegisterChunk.dehydrate(chunkid);
if (this.isConnected) this.ws.send(buffer); if (this.readyState === WebSocket.OPEN) {
this.ws.send(buffer);
}
const pos = chunks.indexOf(chunkid); const pos = chunks.indexOf(chunkid);
if (~pos) chunks.splice(pos, 1); if (~pos) chunks.splice(pos, 1);
} }
@ -129,17 +145,15 @@ class SocketClient extends EventEmitter {
* @param pixel Array of [[offset, color],...] pixels within chunk * @param pixel Array of [[offset, color],...] pixels within chunk
*/ */
requestPlacePixels( requestPlacePixels(
i: number, j: number, i, j,
pixels: Array, pixels,
) { ) {
const buffer = PixelUpdate.dehydrate(i, j, pixels); const buffer = PixelUpdate.dehydrate(i, j, pixels);
this.sendWhenReady(buffer); this.sendWhenReady(buffer);
} }
sendChatMessage(message, channelId) { sendChatMessage(message, channelId) {
if (this.isConnected) { this.sendWhenReady(JSON.stringify([message, channelId]));
this.ws.send(JSON.stringify([message, channelId]));
}
} }
onMessage({ data: message }) { onMessage({ data: message }) {
@ -196,6 +210,10 @@ class SocketClient extends EventEmitter {
this.emit('pixelReturn', PixelReturn.hydrate(data)); this.emit('pixelReturn', PixelReturn.hydrate(data));
break; break;
case OnlineCounter.OP_CODE: case OnlineCounter.OP_CODE:
/*
* using online counter as sign-of-life ping
*/
this.timeLastPing = Date.now();
this.emit('onlineCounter', OnlineCounter.hydrate(data)); this.emit('onlineCounter', OnlineCounter.hydrate(data));
break; break;
case CoolDownPacket.OP_CODE: case CoolDownPacket.OP_CODE:
@ -215,9 +233,9 @@ class SocketClient extends EventEmitter {
onClose(e) { onClose(e) {
this.emit('close'); this.emit('close');
this.ws = null; this.ws = null;
this.isConnected = false; this.readyState = WebSocket.CONNECTING;
// reconnect in 1s if last connect was longer than 7s ago, else 5s // reconnect in 1s if last connect was longer than 7s ago, else 5s
const timeout = this.timeConnected < Date.now() - 7000 ? 1000 : 5000; const timeout = this.timeLastConnecting < Date.now() - 7000 ? 1000 : 5000;
console.warn( console.warn(
`Socket is closed. Reconnect will be attempted in ${timeout} ms.`, `Socket is closed. Reconnect will be attempted in ${timeout} ms.`,
e.reason, e.reason,
@ -226,18 +244,14 @@ class SocketClient extends EventEmitter {
setTimeout(() => this.connect(), 5000); setTimeout(() => this.connect(), 5000);
} }
close() {
this.ws.close();
}
reconnect() { reconnect() {
if (this.isConnected) { if (this.readyState === WebSocket.OPEN) {
this.isConnected = false; this.readyState = WebSocket.CLOSING;
console.log('Restarting WebSocket'); console.log('Restarting WebSocket');
this.ws.onclose = null; this.ws.onclose = null;
this.ws.onmessage = null; this.ws.onmessage = null;
this.ws.close();
this.ws = null; this.ws = null;
this.ws.close();
this.connect(); this.connect();
} }
} }

View File

@ -60,8 +60,8 @@ class SocketServer {
this.broadcast = this.broadcast.bind(this); this.broadcast = this.broadcast.bind(this);
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this); this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
this.reloadUser = this.reloadUser.bind(this); this.reloadUser = this.reloadUser.bind(this);
this.ping = this.ping.bind(this);
this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this); this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this);
this.checkHealth = this.checkHealth.bind(this);
} }
initialize() { initialize() {
@ -83,9 +83,8 @@ class SocketServer {
}); });
wss.on('connection', async (ws, req) => { wss.on('connection', async (ws, req) => {
ws.isAlive = true; ws.timeLastMsg = Date.now();
ws.canvasId = null; ws.canvasId = null;
ws.startDate = Date.now();
const user = await authenticateClient(req); const user = await authenticateClient(req);
if (!user) { if (!user) {
ws.close(); ws.close();
@ -103,16 +102,13 @@ class SocketServer {
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`); logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
}); });
ws.on('pong', () => {
ws.isAlive = true;
});
ws.on('close', () => { ws.on('close', () => {
ipCounter.delete(ip); ipCounter.delete(ip);
this.deleteAllChunks(ws); this.deleteAllChunks(ws);
}); });
ws.on('message', (data, isBinary) => { ws.on('message', (data, isBinary) => {
ws.timeLastMsg = Date.now();
if (isBinary) { if (isBinary) {
this.onBinaryMessage(data, ws); this.onBinaryMessage(data, ws);
} else { } else {
@ -179,7 +175,7 @@ class SocketServer {
}); });
setInterval(this.onlineCounterBroadcast, 10 * 1000); setInterval(this.onlineCounterBroadcast, 10 * 1000);
setInterval(this.ping, 15 * 1000); setInterval(this.checkHealth, 15 * 1000);
} }
verifyClient(info, done) { verifyClient(info, done) {
@ -351,16 +347,15 @@ class SocketServer {
}); });
} }
ping() { checkHealth() {
const ts = Date.now() - 15000;
this.wss.clients.forEach((ws) => { this.wss.clients.forEach((ws) => {
if (!ws.isAlive) { if (
if (ws.user) { ws.readyState === WebSocket.OPEN
logger.info(`Killing dead websocket from ${ws.user.ip}`); && ts > ws.timeLastMsg
} ) {
logger.info(`Killing dead websocket from ${ws.user.ip}`);
ws.terminate(); ws.terminate();
} else {
ws.isAlive = false;
ws.ping(() => {});
} }
}); });
} }

View File

@ -5,9 +5,6 @@ export default {
dehydrate() { dehydrate() {
// Server (sender) // Server (sender)
const buffer = new ArrayBuffer(1); return new Uint8Array([OP_CODE]).buffer;
const view = new DataView(buffer);
view.setInt8(0, OP_CODE);
return buffer;
}, },
}; };

View File

@ -0,0 +1,10 @@
const OP_CODE = 0xB0;
export default {
OP_CODE,
dehydrate() {
// Client (sender)
return new Uint8Array([OP_CODE]).buffer;
},
};