From 870f827e49b0ae680b84f3c248ba5f6dcd77a24e Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 31 May 2021 20:05:41 +0200 Subject: [PATCH] log only 5 min after game end + some type hinting :P --- build/server/main.js | 44 +++++++++++++++++++++--------- src/common/Types.ts | 1 + src/common/Util.ts | 7 +++-- src/frontend/Communication.ts | 14 +++++----- src/frontend/game.ts | 3 ++- src/server/Game.ts | 26 +++++++++++------- src/server/GameLog.ts | 17 ++++++++++++ src/server/WebSocketServer.ts | 2 +- src/server/main.ts | 50 ++++++++++++++++++++--------------- 9 files changed, 108 insertions(+), 56 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 108304e..39780ab 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1186,6 +1186,17 @@ const PUBLIC_DIR = `${BASE_DIR}/build/public/`; const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`; const DB_FILE = `${BASE_DIR}/data/db.sqlite`; +const POST_GAME_LOG_DURATION = 5 * Time.MIN; +const shouldLog = (finishTs, currentTs) => { + // when not finished yet, always log + if (!finishTs) { + return true; + } + // in finished games, log max POST_GAME_LOG_DURATION after + // the game finished, to record winning dance moves etc :P + const timeSinceGameEnd = currentTs - finishTs; + return timeSinceGameEnd <= POST_GAME_LOG_DURATION; +}; const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; const create = (gameId) => { const file = filename(gameId); @@ -1237,6 +1248,7 @@ const get = async (gameId, offset = 0, size = 10000) => { }); }; var GameLog = { + shouldLog, create, exists, log: _log, @@ -1689,21 +1701,25 @@ async function createGame(gameId, targetTiles, image, ts, scoreMode) { GameStorage.setDirty(gameId); } function addPlayer(gameId, playerId, ts) { - const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); - if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); - } - else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId); + const diff = ts - GameCommon.getStartTs(gameId); + if (idx === -1) { + GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); + } + else { + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + } } GameCommon.addPlayer(gameId, playerId, ts); GameStorage.setDirty(gameId); } function handleInput(gameId, playerId, input, ts) { - const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); + if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId); + const diff = ts - GameCommon.getStartTs(gameId); + GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); + } const ret = GameCommon.handleInput(gameId, playerId, input, ts); GameStorage.setDirty(gameId); return ret; @@ -2069,6 +2085,8 @@ wss.on('message', async ({ socket, data }) => { const proto = socket.protocol.split('|'); const clientId = proto[0]; const gameId = proto[1]; + // TODO: maybe handle different types of data + // (but atm only string comes through) const msg = JSON.parse(data); const msgType = msg[0]; switch (msgType) { @@ -2152,12 +2170,12 @@ const gracefulShutdown = (signal) => { process.exit(); }; // used by nodemon -process.once('SIGUSR2', function () { +process.once('SIGUSR2', () => { gracefulShutdown('SIGUSR2'); }); -process.once('SIGINT', function () { +process.once('SIGINT', () => { gracefulShutdown('SIGINT'); }); -process.once('SIGTERM', function () { +process.once('SIGTERM', () => { gracefulShutdown('SIGTERM'); }); diff --git a/src/common/Types.ts b/src/common/Types.ts index f00c3e2..edb4a1e 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -15,6 +15,7 @@ export type Change = Array export type GameEvent = Array +export type ServerEvent = Array export type ClientEvent = Array export type EncodedPlayer = FixedLengthArray<[ diff --git a/src/common/Util.ts b/src/common/Util.ts index 020c568..a24ed05 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -29,8 +29,11 @@ const pad = (x: number, pad: string): string => { return pad.substr(0, pad.length - str.length) + str } -export const logger = (...pre: Array) => { - const log = (m: 'log'|'info'|'error') => (...args: Array) => { +type LogArgs = Array +type LogFn = (...args: LogArgs) => void + +export const logger = (...pre: string[]): { log: LogFn, error: LogFn, info: LogFn } => { + const log = (m: 'log'|'info'|'error') => (...args: LogArgs): void => { const d = new Date() const hh = pad(d.getHours(), '00') const mm = pad(d.getMinutes(), '00') diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 0e1e9ea..5301faa 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -1,6 +1,6 @@ "use strict" -import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types' +import { ClientEvent, EncodedGame, GameEvent, ReplayData, ServerEvent } from '../common/Types' import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' @@ -17,8 +17,8 @@ const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) let ws: WebSocket -let missedMessages: Array = [] -let changesCallback = (msg: Array) => { +let missedMessages: ServerEvent[] = [] +let changesCallback = (msg: ServerEvent) => { missedMessages.push(msg) } @@ -27,7 +27,7 @@ let connectionStateChangeCallback = (state: number) => { missedStateChanges.push(state) } -function onServerChange(callback: (msg: Array) => void): void { +function onServerChange(callback: (msg: ServerEvent) => void): void { changesCallback = callback for (const missedMessage of missedMessages) { changesCallback(missedMessage) @@ -77,8 +77,8 @@ function connect( setConnectionState(CONN_STATE_CONNECTED) send([Protocol.EV_CLIENT_INIT]) } - ws.onmessage = (e) => { - const msg = JSON.parse(e.data) + ws.onmessage = (e: MessageEvent) => { + const msg: ServerEvent = JSON.parse(e.data) const msgType = msg[0] if (msgType === Protocol.EV_SERVER_INIT) { const game = msg[1] @@ -102,7 +102,7 @@ function connect( throw `[ 2021-05-15 onerror ]` } - ws.onclose = (e) => { + ws.onclose = (e: CloseEvent) => { if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { setConnectionState(CONN_STATE_CLOSED) } else { diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 36f12ea..c1e4317 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -21,6 +21,7 @@ import { ReplayData, Timestamp, GameEvent, + ServerEvent, } from '../common/Types' declare global { interface Window { @@ -485,7 +486,7 @@ export async function main( } if (MODE === MODE_PLAY) { - Communication.onServerChange((msg) => { + Communication.onServerChange((msg: ServerEvent) => { const msgType = msg[0] const evClientId = msg[1] const evClientSeq = msg[2] diff --git a/src/server/Game.ts b/src/server/Game.ts index a35a8d7..8ac87ff 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -1,12 +1,14 @@ import GameCommon from './../common/GameCommon' import { Change, Game, Input, ScoreMode, Timestamp } from './../common/Types' -import Util from './../common/Util' +import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import GameLog from './GameLog' import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' import Protocol from './../common/Protocol' import GameStorage from './GameStorage' +const log = logger('Game.ts') + async function createGameObject( gameId: string, targetTiles: number, @@ -49,12 +51,14 @@ async function createGame( } function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { - const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) - if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff) - } else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) + if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId) + const diff = ts - GameCommon.getStartTs(gameId) + if (idx === -1) { + GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff) + } else { + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) + } } GameCommon.addPlayer(gameId, playerId, ts) @@ -67,9 +71,11 @@ function handleInput( input: Input, ts: Timestamp ): Array { - const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff) + if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId) + const diff = ts - GameCommon.getStartTs(gameId) + GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff) + } const ret = GameCommon.handleInput(gameId, playerId, input, ts) GameStorage.setDirty(gameId) diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index ca1d306..d764304 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,11 +1,27 @@ import fs from 'fs' import readline from 'readline' import stream from 'stream' +import Time from '../common/Time' +import { Timestamp } from '../common/Types' import { logger } from './../common/Util' import { DATA_DIR } from './../server/Dirs' const log = logger('GameLog.js') +const POST_GAME_LOG_DURATION = 5 * Time.MIN + +const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { + // when not finished yet, always log + if (!finishTs) { + return true + } + + // in finished games, log max POST_GAME_LOG_DURATION after + // the game finished, to record winning dance moves etc :P + const timeSinceGameEnd = currentTs - finishTs + return timeSinceGameEnd <= POST_GAME_LOG_DURATION +} + const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` const create = (gameId: string): void => { @@ -66,6 +82,7 @@ const get = async ( } export default { + shouldLog, create, exists, log: _log, diff --git a/src/server/WebSocketServer.ts b/src/server/WebSocketServer.ts index 02f546f..7f648ba 100644 --- a/src/server/WebSocketServer.ts +++ b/src/server/WebSocketServer.ts @@ -56,7 +56,7 @@ class WebSocketServer { socket.close() return } - socket.on('message', (data: any) => { + socket.on('message', (data: WebSocket.Data) => { log.log(`ws`, socket.protocol, data) this.evt.dispatch('message', {socket, data}) }) diff --git a/src/server/main.ts b/src/server/main.ts index 4737326..4bfbfb8 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -19,7 +19,7 @@ import { UPLOAD_DIR, } from './Dirs' import GameCommon from '../common/GameCommon' -import { Game as GameType, GameSettings, ScoreMode } from '../common/Types' +import { ServerEvent, Game as GameType, GameSettings, ScoreMode } from '../common/Types' import GameStorage from './GameStorage' import Db from './Db' @@ -57,14 +57,14 @@ const storage = multer.diskStorage({ }) const upload = multer({storage}).single('file'); -app.get('/api/conf', (req, res) => { +app.get('/api/conf', (req, res): void => { res.send({ WS_ADDRESS: config.ws.connectstring, }) }) -app.get('/api/replay-data', async (req, res) => { - const q = req.query as any +app.get('/api/replay-data', async (req, res): Promise => { + const q: Record = req.query const offset = parseInt(q.offset, 10) || 0 if (offset < 0) { res.status(400).send({ reason: 'bad offset' }) @@ -95,8 +95,8 @@ app.get('/api/replay-data', async (req, res) => { res.send({ log, game: game ? Util.encodeGame(game) : null }) }) -app.get('/api/newgame-data', (req, res) => { - const q = req.query as any +app.get('/api/newgame-data', (req, res): void => { + const q: Record = req.query const tagSlugs: string[] = q.tags ? q.tags.split(',') : [] res.send({ images: Images.allImagesFromDb(db, tagSlugs, q.sort), @@ -104,10 +104,10 @@ app.get('/api/newgame-data', (req, res) => { }) }) -app.get('/api/index-data', (req, res) => { +app.get('/api/index-data', (req, res): void => { const ts = Time.timestamp() const games = [ - ...GameCommon.getAllGames().map((game: any) => ({ + ...GameCommon.getAllGames().map((game: GameType) => ({ id: game.id, hasReplay: GameLog.exists(game.id), started: GameCommon.getStartTs(game.id), @@ -131,7 +131,7 @@ interface SaveImageRequestData { tags: string[] } -const setImageTags = (db: Db, imageId: number, tags: string[]) => { +const setImageTags = (db: Db, imageId: number, tags: string[]): void => { tags.forEach((tag: string) => { const slug = Util.slug(tag) const id = db.upsert('categories', { slug, title: tag }, { slug }, 'id') @@ -144,7 +144,7 @@ const setImageTags = (db: Db, imageId: number, tags: string[]) => { }) } -app.post('/api/save-image', express.json(), (req, res) => { +app.post('/api/save-image', express.json(), (req, res): void => { const data = req.body as SaveImageRequestData db.update('images', { title: data.title, @@ -160,8 +160,8 @@ app.post('/api/save-image', express.json(), (req, res) => { res.send({ ok: true }) }) -app.post('/api/upload', (req, res) => { - upload(req, res, async (err: any) => { +app.post('/api/upload', (req, res): void => { + upload(req, res, async (err: any): Promise => { if (err) { log.log(err) res.status(400).send("Something went wrong!"); @@ -189,7 +189,7 @@ app.post('/api/upload', (req, res) => { }) }) -app.post('/api/newgame', express.json(), async (req, res) => { +app.post('/api/newgame', express.json(), async (req, res): Promise => { const gameSettings = req.body as GameSettings log.log(gameSettings) const gameId = Util.uniqId() @@ -211,13 +211,15 @@ app.use('/', express.static(PUBLIC_DIR)) const wss = new WebSocketServer(config.ws); -const notify = (data: any, sockets: Array) => { +const notify = (data: ServerEvent, sockets: Array): void => { for (const socket of sockets) { wss.notifyOne(data, socket) } } -wss.on('close', async ({socket} : {socket: WebSocket}) => { +wss.on('close', async ( + {socket} : { socket: WebSocket } +): Promise => { try { const proto = socket.protocol.split('|') // const clientId = proto[0] @@ -228,12 +230,16 @@ wss.on('close', async ({socket} : {socket: WebSocket}) => { } }) -wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { +wss.on('message', async ( + {socket, data} : { socket: WebSocket, data: WebSocket.Data } +): Promise => { try { const proto = socket.protocol.split('|') const clientId = proto[0] const gameId = proto[1] - const msg = JSON.parse(data) + // TODO: maybe handle different types of data + // (but atm only string comes through) + const msg = JSON.parse(data as string) const msgType = msg[0] switch (msgType) { case Protocol.EV_CLIENT_INIT: { @@ -303,7 +309,7 @@ const server = app.listen( wss.listen() -const memoryUsageHuman = () => { +const memoryUsageHuman = (): void => { const totalHeapSize = v8.getHeapStatistics().total_available_size const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2) @@ -322,7 +328,7 @@ const persistInterval = setInterval(() => { memoryUsageHuman() }, config.persistence.interval) -const gracefulShutdown = (signal: any) => { +const gracefulShutdown = (signal: string): void => { log.log(`${signal} received...`) log.log('clearing persist interval...') @@ -342,14 +348,14 @@ const gracefulShutdown = (signal: any) => { } // used by nodemon -process.once('SIGUSR2', function () { +process.once('SIGUSR2', (): void => { gracefulShutdown('SIGUSR2') }) -process.once('SIGINT', function () { +process.once('SIGINT', (): void => { gracefulShutdown('SIGINT') }) -process.once('SIGTERM', function () { +process.once('SIGTERM', (): void => { gracefulShutdown('SIGTERM') })