From: Raphaƫl Jakse Date: Sun, 26 Oct 2025 10:54:38 +0000 (+0100) Subject: #20: Persist chat messages X-Git-Url: https://secure.softndesign.org/git/?a=commitdiff_plain;h=e1debea09a35c8abd0399c4baf664fe60b92305b;p=trivabble.git #20: Persist chat messages --- diff --git a/public/trivabble.js b/public/trivabble.js index 764e210..25e71f7 100644 --- a/public/trivabble.js +++ b/public/trivabble.js @@ -869,9 +869,21 @@ element.classList.add("blink"); } - function chatMessage(sender, content) { + function chatMessage(sender, content, date) { + const msgId = ""; + if (sender && date) { + msgId = "chatmsg-" + date + "--" + sender; + if (document.getElementById(msgId)) { + // we already have that message + return; + } + } + chatMessages.style.width = chatTextarea.offsetWidth + "px"; // HACK to prevent the chat box from getting too big const msgDom = document.createElement("div"); + if (msgId) { + msgDom.id = msgId; + } msgDom.className = "msg"; @@ -913,7 +925,11 @@ return content; } - function handleChatMessage(msg) { + function handleHistoricalChatMessage(msg) { + handleChatMessage(true, msg); + } + + function handleChatMessage(fromHistory, msg) { if (msg.specialMsg) { switch (msg.specialMsg.type) { case "rack": { @@ -946,15 +962,17 @@ } case "highlightCell": { - localHighlightCell(msg.specialMsg.cell); - infoMessage(msg.msg); + if (!fromHistory) { + localHighlightCell(msg.specialMsg.cell); + infoMessage(msg.msg); + } break; } } } if (msg.content) { - chatMessage(msg.sender, msg.content); + chatMessage(msg.sender, msg.content, msg.date); } } @@ -1263,7 +1281,7 @@ } if (data.msg) { - handleChatMessage(data.msg); + handleChatMessage(false, data.msg); } if (typeof data.currentPlayer === "string") { @@ -1333,6 +1351,14 @@ tileCount.textContent = data.remainingLetters; } + if (data.chatHistory) { + data.chatHistory.forEach(handleHistoricalChatMessage); + } + + if (data.chatMessageMaxSize && data.chatMessageMaxSize > 0) { + chatTextarea.maxLength = data.chatMessageMaxSize; + } + if (data.rack) { setRack(data.rack); } diff --git a/server/trivabble-server.js b/server/trivabble-server.js index 06f1cd3..ec1abae 100644 --- a/server/trivabble-server.js +++ b/server/trivabble-server.js @@ -40,6 +40,8 @@ const DEFAULT_BOARD_LABEL = process.env.TRIVABBLE_DEFAULT_BOARD_LABEL || "15x15- const DEFAULT_BAG_FACTOR = 1; const CHECK_FIRST_WORD = 1; // 1: check, 0: not set, -1: no check const FREEZE_PLAYED_TILES = -1; // 1: freeze, 0: not set, -1: no freeze +const CHAT_HISTORY_SIZE_LIMIT = parseInt(process.env.TRIVABBLE_CHAT_HISTORY_SIZE_LIMIT || "50"); +const CHAT_MESSAGE_MAX_SIZE = parseInt(process.env.TRIVABBLE_CHAT_MESSAGE_MAX_SIZE || "1000"); const VERSION = 202508050000; @@ -249,7 +251,7 @@ function newBoard(label) { // if you add something there that stores the player name, please make sure to // update changePlayerName // eslint-disable-next-line max-params -Game.prototype.init = function (lang, label, factor, checkFirstWord, freezePlayedTiles) { +Game.prototype.init = function (lang, label, factor, checkFirstWord, freezePlayedTiles, chatHistory) { this.label = label || DEFAULT_BOARD_LABEL; this.factor = factor || DEFAULT_BAG_FACTOR; this.board = newBoard(this.label); @@ -263,6 +265,7 @@ Game.prototype.init = function (lang, label, factor, checkFirstWord, freezePlaye this.freezePlayedTiles = freezePlayedTiles || FREEZE_PLAYED_TILES; this.lastUpdated = new Date(); this.currentPlayer = ""; + this.chatHistory = chatHistory || []; this.bag = expandAndShuffleBag(bag, this.factor); }; @@ -281,6 +284,7 @@ Game.prototype.toJSON = function () { checkFirstWord: this.checkFirstWord, freezePlayedTiles: this.freezePlayedTiles, lastUpdated: this.lastUpdated.toISOString(), + chatHistory: this.chatHistory, currentPlayer: this.currentPlayer }; }; @@ -300,6 +304,7 @@ Game.fromJSON = function (obj) { game.freezePlayedTiles = obj.freezePlayedTiles || FREEZE_PLAYED_TILES; game.lastUpdated = obj.lastUpdated ? new Date(obj.lastUpdated) : new Date(); game.currentPlayer = obj.currentPlayer || ""; + game.chatHistory = obj.chatHistory || []; return game; }; @@ -493,7 +498,7 @@ Game.prototype.bagPushLetter = function (letter, player) { }; Game.prototype.reset = function (player) { - this.init(this.lang, this.label, this.factor, this.checkFirstWord, this.freezePlayedTiles); + this.init(this.lang, this.label, this.factor, this.checkFirstWord, this.freezePlayedTiles, this.chatHistory); this.pendingEvents.push({ player: player, action: "reset", @@ -548,10 +553,26 @@ Game.prototype.changePlayerName = function (playerName, newName) { newName: n }] }); + + // We don't currently update the chat history with the new name. + // This is on purpose, but maybe we should update the name there too? + this.commit(); return n; }; +Game.prototype.storeAndDispatchChatMessage = function (msg) { + if (CHAT_HISTORY_SIZE_LIMIT !== -1) { + this.chatHistory.push(msg); + } + + while (this.chatHistory.length > CHAT_HISTORY_SIZE_LIMIT) { + this.chatHistory.shift(); + } + + this.pendingEvents.push({msg}); +}; + function newGameId() { let number; @@ -643,6 +664,9 @@ function handleCommand(cmdNumber, message, response) { tileOwner: game.tileOwner, remainingLetters: game.bag.length, letterValues: game.letterValues, + chatHistory: game.chatHistory, + chatHistoryLimit: CHAT_HISTORY_SIZE_LIMIT, + chatMessageMaxSize: CHAT_MESSAGE_MAX_SIZE, version: VERSION }); break; @@ -906,13 +930,18 @@ function handleCommand(cmdNumber, message, response) { case "msg": { - game.pendingEvents.push({ - msg: { - sender: playerName, - content: cmd.msg, - specialMsg: cmd.specialMsg - } - }); + const msg = { + date: new Date(), + sender: playerName, + content: cmd.msg, + specialMsg: cmd.specialMsg + }; + + if (CHAT_MESSAGE_MAX_SIZE !== -1 && msg.content.length > CHAT_MESSAGE_MAX_SIZE) { + msg.content = msg.content.substr(0, CHAT_MESSAGE_MAX_SIZE); + } + + game.storeAndDispatchChatMessage(msg); reply(message, response, cmdNumber, {error: 0}); break; @@ -931,6 +960,7 @@ function handleCommands(message, responseAndType) { if (!message.cmds || !message.cmds.length) { const {gameNumber, game} = joinGame(message.gameNumber, message); + // FIXME: deduplicate with joinGame? writeMessage(responseAndType, JSON.stringify({ playerName: message.playerName, @@ -947,6 +977,9 @@ function handleCommands(message, responseAndType) { remainingLetters: game.bag.length, checkFirstWord: game.checkFirstWord, freezePlayedTiles: game.freezePlayedTiles, + chatHistory: game.chatHistory, + chatHistoryLimit: CHAT_HISTORY_SIZE_LIMIT, + chatMessageMaxSize: CHAT_MESSAGE_MAX_SIZE, version: VERSION }) );