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);
};
/**