Skip to content

Commit

Permalink
chat: New option to completely disable chat
Browse files Browse the repository at this point in the history
  • Loading branch information
rhansen committed May 6, 2022
1 parent 768c817 commit a08e68f
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 119 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Notable enhancements and fixes

* New `enableIntegratedChat` setting makes it possible to completely disable the
built-in chat feature (not just hide it).
* Improvements to login session management:
* `express_sid` cookies and `sessionstorage:*` database records are no longer
created unless `requireAuthentication` is `true` (or a plugin causes them to
Expand Down
19 changes: 10 additions & 9 deletions doc/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,16 @@ The `settings.json.docker` available by default allows to control almost every s

### General

| Variable | Description | Default |
| ------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TITLE` | The name of the instance | `Etherpad` |
| `FAVICON` | favicon default name, or a fully specified URL to your own favicon | `favicon.ico` |
| `DEFAULT_PAD_TEXT` | The default text of a pad | `Welcome to Etherpad! This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents! Get involved with Etherpad at https://etherpad.org` |
| `IP` | IP which etherpad should bind at. Change to `::` for IPv6 | `0.0.0.0` |
| `PORT` | port which etherpad should bind at | `9001` |
| `ADMIN_PASSWORD` | the password for the `admin` user (leave unspecified if you do not want to create it) | |
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |
| Variable | Description | Default |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TITLE` | The name of the instance | `Etherpad` |
| `FAVICON` | favicon default name, or a fully specified URL to your own favicon | `favicon.ico` |
| `DEFAULT_PAD_TEXT` | The default text of a pad | `Welcome to Etherpad! This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents! Get involved with Etherpad at https://etherpad.org` |
| `ENABLE_INTEGRATED_CHAT` | Whether to enable the built-in chat feature. Set this to false if you prefer to use a plugin to provide chat functionality or simply do not want the feature. | true |
| `IP` | IP which etherpad should bind at. Change to `::` for IPv6 | `0.0.0.0` |
| `PORT` | port which etherpad should bind at | `9001` |
| `ADMIN_PASSWORD` | the password for the `admin` user (leave unspecified if you do not want to create it) | |
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |


### Database
Expand Down
8 changes: 8 additions & 0 deletions settings.json.docker
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@
*/
"defaultPadText" : "${DEFAULT_PAD_TEXT:Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https:\/\/etherpad.org\n}",

/*
* Whether to enable the built-in chat feature. Set this to false if you
* prefer to use a plugin to provide chat functionality or simply do not want
* the feature.
*/
"enableIntegratedChat": "${ENABLE_INTEGRATED_CHAT:true}",

/*
* Default Pad behavior.
*
Expand All @@ -231,6 +238,7 @@
"padOptions": {
"noColors": "${PAD_OPTIONS_NO_COLORS:false}",
"showControls": "${PAD_OPTIONS_SHOW_CONTROLS:true}",
// To completely disable chat, set enableIntegratedChat to false.
"showChat": "${PAD_OPTIONS_SHOW_CHAT:true}",
"showLineNumbers": "${PAD_OPTIONS_SHOW_LINE_NUMBERS:true}",
"useMonospaceFont": "${PAD_OPTIONS_USE_MONOSPACE_FONT:false}",
Expand Down
8 changes: 8 additions & 0 deletions settings.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@
*/
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https:\/\/etherpad.org\n",

/*
* Whether to enable the built-in chat feature. Set this to false if you
* prefer to use a plugin to provide chat functionality or simply do not want
* the feature.
*/
"enableIntegratedChat": true,

/*
* Default Pad behavior.
*
Expand All @@ -232,6 +239,7 @@
"padOptions": {
"noColors": false,
"showControls": true,
// To completely disable chat, set enableIntegratedChat to false.
"showChat": true,
"showLineNumbers": true,
"useMonospaceFont": false,
Expand Down
7 changes: 6 additions & 1 deletion src/ep.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
"padCheck": "ep_etherpad-lite/node/chat",
"padCopy": "ep_etherpad-lite/node/chat",
"padLoad": "ep_etherpad-lite/node/chat",
"padRemove": "ep_etherpad-lite/node/chat",
"padRemove": "ep_etherpad-lite/node/chat"
}
},
{
"name": "chatAlwaysLoaded",
"hooks": {
"socketio": "ep_etherpad-lite/node/chat"
}
},
Expand Down
25 changes: 25 additions & 0 deletions src/node/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ const pad = require('./db/Pad');
const padManager = require('./db/PadManager');
const padMessageHandler = require('./handler/PadMessageHandler');
const promises = require('./utils/promises');
const settings = require('./utils/Settings');

let socketio;

const appendChatMessage = async (pad, msg) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
pad.chatHead++;
await Promise.all([
// Don't save the display name in the database because the user can change it at any time. The
Expand All @@ -26,6 +30,9 @@ const appendChatMessage = async (pad, msg) => {
};

const getChatMessage = async (pad, entryNum) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
const entry = await pad.db.get(`pad:${pad.id}:chat:${entryNum}`);
if (entry == null) return null;
const message = ChatMessage.fromObject(entry);
Expand All @@ -34,6 +41,9 @@ const getChatMessage = async (pad, entryNum) => {
};

const getChatMessages = async (pad, start, end) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
const entries = await Promise.all(
[...Array(end + 1 - start).keys()].map((i) => getChatMessage(pad, start + i)));

Expand All @@ -50,6 +60,9 @@ const getChatMessages = async (pad, start, end) => {
};

const sendChatMessageToPadClients = async (message, padId) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
const pad = await padManager.getPad(padId, null, message.authorId);
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
// appendChatMessage() ignores the displayName property so we don't need to wait for
Expand All @@ -66,6 +79,7 @@ const sendChatMessageToPadClients = async (message, padId) => {
exports.clientVars = (hookName, {pad: {chatHead}}) => ({chatHead});

exports.eejsBlock_mySettings = (hookName, context) => {
if (!settings.enableIntegratedChat) return;
context.content += `
<p class="hide-for-mobile">
<input type="checkbox" id="options-stickychat">
Expand All @@ -79,6 +93,7 @@ exports.eejsBlock_mySettings = (hookName, context) => {
};

exports.eejsBlock_stickyContainer = (hookName, context) => {
if (!settings.enableIntegratedChat) return;
/* eslint-disable max-len */
context.content += `
<div id="chaticon" class="visible" title="Chat (Alt C)">
Expand Down Expand Up @@ -124,6 +139,7 @@ exports.exportEtherpad = async (hookName, {pad, data, dstPadId}) => {
};

exports.handleMessage = async (hookName, {message, sessionInfo, socket}) => {
if (!settings.enableIntegratedChat) return;
const {authorId, padId, readOnly} = sessionInfo;
if (message.type !== 'COLLABROOM' || readOnly) return;
switch (message.data.type) {
Expand Down Expand Up @@ -230,6 +246,9 @@ api.registerChatHandlers({
* {code: 1, message:"padID does not exist", data: null}
*/
appendChatMessage: async (padID, text, authorID, time) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
if (typeof text !== 'string') throw new CustomError('text is not a string', 'apierror');
if (time === undefined || !Number.isInteger(Number.parseInt(time))) time = Date.now();
await sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
Expand All @@ -244,6 +263,9 @@ api.registerChatHandlers({
* {code: 1, message:"padID does not exist", data: null}
*/
getChatHead: async (padID) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
const pad = await getPadSafe(padID);
return {chatHead: pad.chatHead};
},
Expand All @@ -263,6 +285,9 @@ api.registerChatHandlers({
* {code: 1, message:"padID does not exist", data: null}
*/
getChatHistory: async (padID, start, end) => {
if (!settings.enableIntegratedChat) {
throw new Error('integrated chat is disabled (see enableIntegratedChat in settings.json)');
}
if (start && end) {
if (start < 0) throw new CustomError('start is below zero', 'apierror');
if (end < 0) throw new CustomError('end is below zero', 'apierror');
Expand Down
8 changes: 5 additions & 3 deletions src/node/hooks/express/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ const tar = (() => {
'pad_impexp.js',
'pad_savedrevs.js',
'pad_connectionstatus.js',
'ChatMessage.js',
'chat.js',
...settings.enableIntegratedChat ? [
'ChatMessage.js',
'chat.js',
'$tinycon/tinycon.js',
] : [],
'vendors/gritter.js',
'$js-cookie/dist/js.cookie.js',
'$tinycon/tinycon.js',
'vendors/farbtastic.js',
'skin_variants.js',
'socketio.js',
Expand Down
6 changes: 4 additions & 2 deletions src/node/hooks/express/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ exports.expressPreSession = async (hookName, {app}) => {
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
if (plugin === 'ep_etherpad-lite' && !settings.enableAdminUITests &&
spec.startsWith('admin')) continue;
if (plugin === 'ep_etherpad-lite') {
if (!settings.enableAdminUITests && spec.startsWith('admin')) continue;
if (!settings.enableIntegratedChat && spec.startsWith('chat')) continue;
}
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
}
}));
Expand Down
2 changes: 1 addition & 1 deletion src/node/utils/Minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const LIBRARY_WHITELIST = [
'js-cookie',
'security',
'split-grid',
'tinycon',
...settings.enableIntegratedChat ? ['tinycon'] : [],
'underscore',
'unorm',
];
Expand Down
6 changes: 6 additions & 0 deletions src/node/utils/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ exports.defaultPadText = [
'Etherpad on Github: https://github.com/ether/etherpad-lite',
].join('\n');

/**
* Whether to enable the built-in chat feature. Set this to false if you prefer to use a plugin to
* provide chat functionality or simply do not want the feature.
*/
exports.enableIntegratedChat = true;

/**
* The default Pad Settings for a user (Can be overridden by changing the setting
*/
Expand Down
4 changes: 4 additions & 0 deletions src/static/js/pluginfw/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const hooks = require('./hooks');
const log4js = require('log4js');
const path = require('path');
const runCmd = require('../../../node/utils/run_cmd');
const settings = require('../../../node/utils/Settings');
const tsort = require('./tsort');
const pluginUtils = require('./shared');
const defs = require('./plugin_defs');
Expand Down Expand Up @@ -136,6 +137,9 @@ const loadPlugin = async (packages, pluginName, plugins, parts) => {
const data = await fs.readFile(pluginPath);
try {
const plugin = JSON.parse(data);
if (pluginName === 'ep_etherpad-lite' && !settings.enableIntegratedChat) {
plugin.parts = plugin.parts.filter((part) => part.name !== 'chat');
}
plugin.package = packages[pluginName];
plugins[pluginName] = plugin;
for (const part of plugin.parts) {
Expand Down
52 changes: 51 additions & 1 deletion src/tests/backend/specs/api/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const assert = require('assert').strict;
const common = require('../../common');
const plugins = require('../../../../static/js/pluginfw/plugins');
const settings = require('../../../../node/utils/Settings');

let agent;
const apiKey = common.apiKey;
Expand All @@ -13,7 +15,12 @@ const timestamp = Date.now();
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;

describe(__filename, function () {
const backups = {settings: {}};

before(async function () {
backups.settings.enableIntegratedChat = settings.enableIntegratedChat;
settings.enableIntegratedChat = true;
await plugins.update();
agent = await common.init();
await agent.get('/api/')
.expect(200)
Expand All @@ -37,7 +44,16 @@ describe(__filename, function () {
});
});

describe('message sequence', function () {
after(async function () {
Object.assign(settings, backups.settings);
await plugins.update();
});

describe('settings.enableIntegratedChat = true', function () {
beforeEach(async function () {
settings.enableIntegratedChat = true;
});

it('appendChatMessage', async function () {
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
`&authorID=${authorID}&time=${timestamp}`)
Expand Down Expand Up @@ -68,6 +84,40 @@ describe(__filename, function () {
});
});
});

describe('settings.enableIntegratedChat = false', function () {
beforeEach(async function () {
settings.enableIntegratedChat = false;
});

it('appendChatMessage returns an error', async function () {
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
`&authorID=${authorID}&time=${timestamp}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});

it('getChatHead returns an error', async function () {
await agent.get(`${endPoint('getChatHead')}&padID=${padID}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});

it('getChatHistory returns an error', async function () {
await agent.get(`${endPoint('getChatHistory')}&padID=${padID}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});
});
});

function makeid() {
Expand Down
Loading

0 comments on commit a08e68f

Please sign in to comment.