From 8d5fdd7dc930e148936a98e479019bd9d6259adb Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Tue, 7 Dec 2021 03:24:26 -0500 Subject: [PATCH] chat: Move chat message handling to `handleMessage` hook --- CHANGELOG.md | 3 ++ doc/api/hooks_server-side.md | 2 +- src/ep.json | 4 +- src/node/chat.js | 69 +++++++++++++++++++++++++++ src/node/db/API.js | 5 +- src/node/handler/PadMessageHandler.js | 55 +++------------------ 6 files changed, 87 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b578adac7..94cfe4ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,9 @@ * `padUpdate`: The `author` context property is deprecated; use the new `authorId` context property instead. Also, the hook now runs asynchronously. * Chat API deprecations and removals (no replacements planned): + * Server-side: + * The `sendChatMessageToPadClients()` function in + `src/node/handler/PadMessageHandler.js` is deprecated. * Client-side: * The `pad.determineChatVisibility()` method was removed. * The `pad.determineChatAndUsersVisibility()` method was removed. diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 5998c247e..d786fdc5c 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -1086,7 +1086,7 @@ exports.userLeave = async (hookName, {author, padId}) => { ## `chatNewMessage` -Called from: `src/node/handler/PadMessageHandler.js` +Called from: `src/node/chat.js` Called when a user (or plugin) generates a new chat message, just before it is saved to the pad and relayed to all connected users. diff --git a/src/ep.json b/src/ep.json index 50c730e05..a7e7af0df 100644 --- a/src/ep.json +++ b/src/ep.json @@ -22,7 +22,9 @@ }, "hooks": { "eejsBlock_mySettings": "ep_etherpad-lite/node/chat", - "eejsBlock_stickyContainer": "ep_etherpad-lite/node/chat" + "eejsBlock_stickyContainer": "ep_etherpad-lite/node/chat", + "handleMessage": "ep_etherpad-lite/node/chat", + "socketio": "ep_etherpad-lite/node/chat" } }, { diff --git a/src/node/chat.js b/src/node/chat.js index 02a863e9e..a7afd8a20 100644 --- a/src/node/chat.js +++ b/src/node/chat.js @@ -1,5 +1,28 @@ 'use strict'; +const ChatMessage = require('../static/js/ChatMessage'); +const api = require('./db/API'); +const authorManager = require('./db/AuthorManager'); +const hooks = require('../static/js/pluginfw/hooks.js'); +const padManager = require('./db/PadManager'); +const padMessageHandler = require('./handler/PadMessageHandler'); + +let socketio; + +const sendChatMessageToPadClients = async (message, padId) => { + const pad = await padManager.getPad(padId, null, message.authorId); + await hooks.aCallAll('chatNewMessage', {message, pad, padId}); + // pad.appendChatMessage() ignores the displayName property so we don't need to wait for + // authorManager.getAuthorName() to resolve before saving the message to the database. + const promise = pad.appendChatMessage(message); + message.displayName = await authorManager.getAuthorName(message.authorId); + socketio.sockets.in(padId).json.send({ + type: 'COLLABROOM', + data: {type: 'CHAT_MESSAGE', message}, + }); + await promise; +}; + exports.eejsBlock_mySettings = (hookName, context) => { context.content += `

@@ -42,3 +65,49 @@ exports.eejsBlock_stickyContainer = (hookName, context) => { `; /* eslint-enable max-len */ }; + +exports.handleMessage = async (hookName, {message, sessionInfo, socket}) => { + const {authorId, padId, readOnly} = sessionInfo; + if (message.type !== 'COLLABROOM' || readOnly) return; + switch (message.data.type) { + case 'CHAT_MESSAGE': { + const chatMessage = ChatMessage.fromObject(message.data.message); + // Don't trust the user-supplied values. + chatMessage.time = Date.now(); + chatMessage.authorId = authorId; + await sendChatMessageToPadClients(chatMessage, padId); + break; + } + case 'GET_CHAT_MESSAGES': { + const {start, end} = message.data; + if (!Number.isInteger(start)) throw new Error(`missing or invalid start: ${start}`); + if (!Number.isInteger(end)) throw new Error(`missing or invalid end: ${end}`); + const count = end - start; + if (count < 0 || count > 100) throw new Error(`invalid number of messages: ${count}`); + const pad = await padManager.getPad(padId, null, authorId); + socket.json.send({ + type: 'COLLABROOM', + data: { + type: 'CHAT_MESSAGES', + messages: await pad.getChatMessages(start, end), + }, + }); + break; + } + default: + return; + } + return null; // Important! Returning null (not undefined!) stops further processing. +}; + +exports.socketio = (hookName, {io}) => { + socketio = io; +}; + +api.registerChatHandlers({ + sendChatMessageToPadClients, +}); + +padMessageHandler.registerLegacyChatHandlers({ + sendChatMessageToPadClients, +}); diff --git a/src/node/db/API.js b/src/node/db/API.js index 42803602c..65b23e752 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -289,6 +289,9 @@ exports.setHTML = async (padID, html, authorId = '') => { * CHAT FUNCTIONS * **************** */ +let chat = null; +exports.registerChatHandlers = (handlers) => chat = handlers; + /** getChatHistory(padId, start, end), returns a part of or the whole chat-history of this pad @@ -363,7 +366,7 @@ exports.appendChatMessage = async (padID, text, authorID, time) => { // @TODO - missing getPadSafe() call ? // save chat message to database and send message to all connected clients - await padMessageHandler.sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID); + await chat.sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID); }; /* *************** diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 5d75895c2..871d0db10 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -40,6 +40,7 @@ const assert = require('assert').strict; const {RateLimiterMemory} = require('rate-limiter-flexible'); const webaccess = require('../hooks/express/webaccess'); +let chat = null; let rateLimiter; let socketio = null; @@ -54,6 +55,8 @@ const addContextToError = (err, pfx) => { return err; }; +exports.registerLegacyChatHandlers = (handlers) => chat = handlers; + exports.socketio = () => { // The rate limiter is created in this hook so that restarting the server resets the limiter. The // settings.commitRateLimiting object is passed directly to the rate limiter so that the limits @@ -335,8 +338,6 @@ exports.handleMessage = async (socket, message) => { await padChannels.enqueue(thisSession.padId, {socket, message}); break; case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message); break; - case 'CHAT_MESSAGE': await handleChatMessage(socket, message); break; - case 'GET_CHAT_MESSAGES': await handleGetChatMessages(socket, message); break; case 'SAVE_REVISION': await handleSaveRevisionMessage(socket, message); break; case 'CLIENT_MESSAGE': { const {type} = message.data.payload; @@ -413,23 +414,10 @@ exports.handleCustomMessage = (padID, msgString) => { socketio.sockets.in(padID).json.send(msg); }; -/** - * Handles a Chat Message - * @param socket the socket.io Socket object for the client - * @param message the message from the client - */ -const handleChatMessage = async (socket, message) => { - const chatMessage = ChatMessage.fromObject(message.data.message); - const {padId, author: authorId} = sessioninfos[socket.id]; - // Don't trust the user-supplied values. - chatMessage.time = Date.now(); - chatMessage.authorId = authorId; - await exports.sendChatMessageToPadClients(chatMessage, padId); -}; - /** * Adds a new chat message to a pad and sends it to connected clients. * + * @deprecated Use chat.sendChatMessageToPadClients() instead. * @param {(ChatMessage|number)} mt - Either a chat message object (recommended) or the timestamp of * the chat message in ms since epoch (deprecated). * @param {string} puId - If `mt` is a chat message object, this is the destination pad ID. @@ -439,40 +427,11 @@ const handleChatMessage = async (socket, message) => { * object as the first argument and the destination pad ID as the second argument instead. */ exports.sendChatMessageToPadClients = async (mt, puId, text = null, padId = null) => { + padutils.warnDeprecated('PadMessageHandler.sendChatMessageToPadClients() is deprecated; ' + + 'use chat.sendChatMessageToPadClients() instead'); const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt); padId = mt instanceof ChatMessage ? puId : padId; - const pad = await padManager.getPad(padId, null, message.authorId); - await hooks.aCallAll('chatNewMessage', {message, pad, padId}); - // pad.appendChatMessage() ignores the displayName property so we don't need to wait for - // authorManager.getAuthorName() to resolve before saving the message to the database. - const promise = pad.appendChatMessage(message); - message.displayName = await authorManager.getAuthorName(message.authorId); - socketio.sockets.in(padId).json.send({ - type: 'COLLABROOM', - data: {type: 'CHAT_MESSAGE', message}, - }); - await promise; -}; - -/** - * Handles the clients request for more chat-messages - * @param socket the socket.io Socket object for the client - * @param message the message from the client - */ -const handleGetChatMessages = async (socket, {data: {start, end}}) => { - if (!Number.isInteger(start)) throw new Error(`missing or invalid start: ${start}`); - if (!Number.isInteger(end)) throw new Error(`missing or invalid end: ${end}`); - const count = end - start; - if (count < 0 || count > 100) throw new Error(`invalid number of messages: ${count}`); - const {padId, author: authorId} = sessioninfos[socket.id]; - const pad = await padManager.getPad(padId, null, authorId); - socket.json.send({ - type: 'COLLABROOM', - data: { - type: 'CHAT_MESSAGES', - messages: await pad.getChatMessages(start, end), - }, - }); + await chat.sendChatMessageToPadClients(message, padId); }; /**