mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
chat: New option to completely disable chat
This commit is contained in:
parent
310a371234
commit
dda284cbe9
13 changed files with 274 additions and 126 deletions
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
### Notable enhancements and fixes
|
### Notable enhancements and fixes
|
||||||
|
|
||||||
|
* New `integratedChat` setting makes it possible to completely disable the
|
||||||
|
built-in chat feature (not just hide it).
|
||||||
* Improvements to login session management:
|
* Improvements to login session management:
|
||||||
* `express_sid` cookies and `sessionstorage:*` database records are no longer
|
* `express_sid` cookies and `sessionstorage:*` database records are no longer
|
||||||
created unless `requireAuthentication` is `true` (or a plugin causes them to
|
created unless `requireAuthentication` is `true` (or a plugin causes them to
|
||||||
|
|
|
@ -80,15 +80,16 @@ The `settings.json.docker` available by default allows to control almost every s
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
| ------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `TITLE` | The name of the instance | `Etherpad` |
|
| `TITLE` | The name of the instance | `Etherpad` |
|
||||||
| `FAVICON` | favicon default name, or a fully specified URL to your own favicon | `favicon.ico` |
|
| `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` |
|
| `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` |
|
| `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 |
|
||||||
| `PORT` | port which etherpad should bind at | `9001` |
|
| `IP` | IP which etherpad should bind at. Change to `::` for IPv6 | `0.0.0.0` |
|
||||||
| `ADMIN_PASSWORD` | the password for the `admin` user (leave unspecified if you do not want to create it) | |
|
| `PORT` | port which etherpad should bind at | `9001` |
|
||||||
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |
|
| `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
|
### Database
|
||||||
|
|
|
@ -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}",
|
"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.
|
||||||
|
*/
|
||||||
|
"integratedChat": "${INTEGRATED_CHAT:true}",
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default Pad behavior.
|
* Default Pad behavior.
|
||||||
*
|
*
|
||||||
|
@ -231,6 +238,7 @@
|
||||||
"padOptions": {
|
"padOptions": {
|
||||||
"noColors": "${PAD_OPTIONS_NO_COLORS:false}",
|
"noColors": "${PAD_OPTIONS_NO_COLORS:false}",
|
||||||
"showControls": "${PAD_OPTIONS_SHOW_CONTROLS:true}",
|
"showControls": "${PAD_OPTIONS_SHOW_CONTROLS:true}",
|
||||||
|
// To completely disable chat, set integratedChat to false.
|
||||||
"showChat": "${PAD_OPTIONS_SHOW_CHAT:true}",
|
"showChat": "${PAD_OPTIONS_SHOW_CHAT:true}",
|
||||||
"showLineNumbers": "${PAD_OPTIONS_SHOW_LINE_NUMBERS:true}",
|
"showLineNumbers": "${PAD_OPTIONS_SHOW_LINE_NUMBERS:true}",
|
||||||
"useMonospaceFont": "${PAD_OPTIONS_USE_MONOSPACE_FONT:false}",
|
"useMonospaceFont": "${PAD_OPTIONS_USE_MONOSPACE_FONT:false}",
|
||||||
|
|
|
@ -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",
|
"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.
|
||||||
|
*/
|
||||||
|
"integratedChat": true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default Pad behavior.
|
* Default Pad behavior.
|
||||||
*
|
*
|
||||||
|
@ -232,6 +239,7 @@
|
||||||
"padOptions": {
|
"padOptions": {
|
||||||
"noColors": false,
|
"noColors": false,
|
||||||
"showControls": true,
|
"showControls": true,
|
||||||
|
// To completely disable chat, set integratedChat to false.
|
||||||
"showChat": true,
|
"showChat": true,
|
||||||
"showLineNumbers": true,
|
"showLineNumbers": true,
|
||||||
"useMonospaceFont": false,
|
"useMonospaceFont": false,
|
||||||
|
|
|
@ -30,7 +30,12 @@
|
||||||
"padCheck": "ep_etherpad-lite/node/chat",
|
"padCheck": "ep_etherpad-lite/node/chat",
|
||||||
"padCopy": "ep_etherpad-lite/node/chat",
|
"padCopy": "ep_etherpad-lite/node/chat",
|
||||||
"padLoad": "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"
|
"socketio": "ep_etherpad-lite/node/chat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,10 +10,14 @@ const hooks = require('../static/js/pluginfw/hooks.js');
|
||||||
const pad = require('./db/Pad');
|
const pad = require('./db/Pad');
|
||||||
const padManager = require('./db/PadManager');
|
const padManager = require('./db/PadManager');
|
||||||
const padMessageHandler = require('./handler/PadMessageHandler');
|
const padMessageHandler = require('./handler/PadMessageHandler');
|
||||||
|
const settings = require('./utils/Settings');
|
||||||
|
|
||||||
let socketio;
|
let socketio;
|
||||||
|
|
||||||
const appendChatMessage = async (pad, msg) => {
|
const appendChatMessage = async (pad, msg) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
pad.chatHead++;
|
pad.chatHead++;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Don't save the display name in the database because the user can change it at any time. The
|
// Don't save the display name in the database because the user can change it at any time. The
|
||||||
|
@ -25,6 +29,9 @@ const appendChatMessage = async (pad, msg) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChatMessage = async (pad, entryNum) => {
|
const getChatMessage = async (pad, entryNum) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
const entry = await pad.db.get(`pad:${pad.id}:chat:${entryNum}`);
|
const entry = await pad.db.get(`pad:${pad.id}:chat:${entryNum}`);
|
||||||
if (entry == null) return null;
|
if (entry == null) return null;
|
||||||
const message = ChatMessage.fromObject(entry);
|
const message = ChatMessage.fromObject(entry);
|
||||||
|
@ -33,6 +40,9 @@ const getChatMessage = async (pad, entryNum) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChatMessages = async (pad, start, end) => {
|
const getChatMessages = async (pad, start, end) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
const entries = await Promise.all(
|
const entries = await Promise.all(
|
||||||
[...Array(end + 1 - start).keys()].map((i) => getChatMessage(pad, start + i)));
|
[...Array(end + 1 - start).keys()].map((i) => getChatMessage(pad, start + i)));
|
||||||
|
|
||||||
|
@ -49,6 +59,9 @@ const getChatMessages = async (pad, start, end) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendChatMessageToPadClients = async (message, padId) => {
|
const sendChatMessageToPadClients = async (message, padId) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
const pad = await padManager.getPad(padId, null, message.authorId);
|
const pad = await padManager.getPad(padId, null, message.authorId);
|
||||||
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
|
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
|
||||||
// appendChatMessage() ignores the displayName property so we don't need to wait for
|
// appendChatMessage() ignores the displayName property so we don't need to wait for
|
||||||
|
@ -65,6 +78,7 @@ const sendChatMessageToPadClients = async (message, padId) => {
|
||||||
exports.clientVars = (hookName, {pad: {chatHead}}) => ({chatHead});
|
exports.clientVars = (hookName, {pad: {chatHead}}) => ({chatHead});
|
||||||
|
|
||||||
exports.eejsBlock_mySettings = (hookName, context) => {
|
exports.eejsBlock_mySettings = (hookName, context) => {
|
||||||
|
if (!settings.integratedChat) return;
|
||||||
context.content += `
|
context.content += `
|
||||||
<p class="hide-for-mobile">
|
<p class="hide-for-mobile">
|
||||||
<input type="checkbox" id="options-stickychat">
|
<input type="checkbox" id="options-stickychat">
|
||||||
|
@ -78,6 +92,7 @@ exports.eejsBlock_mySettings = (hookName, context) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.eejsBlock_stickyContainer = (hookName, context) => {
|
exports.eejsBlock_stickyContainer = (hookName, context) => {
|
||||||
|
if (!settings.integratedChat) return;
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
context.content += `
|
context.content += `
|
||||||
<div id="chaticon" class="visible" title="Chat (Alt C)">
|
<div id="chaticon" class="visible" title="Chat (Alt C)">
|
||||||
|
@ -123,6 +138,7 @@ exports.exportEtherpad = async (hookName, {pad, data, dstPadId}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.handleMessage = async (hookName, {message, sessionInfo, socket}) => {
|
exports.handleMessage = async (hookName, {message, sessionInfo, socket}) => {
|
||||||
|
if (!settings.integratedChat) return;
|
||||||
const {authorId, padId, readOnly} = sessionInfo;
|
const {authorId, padId, readOnly} = sessionInfo;
|
||||||
if (message.type !== 'COLLABROOM' || readOnly) return;
|
if (message.type !== 'COLLABROOM' || readOnly) return;
|
||||||
switch (message.data.type) {
|
switch (message.data.type) {
|
||||||
|
@ -233,6 +249,9 @@ api.registerChatHandlers({
|
||||||
* {code: 1, message:"padID does not exist", data: null}
|
* {code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
appendChatMessage: async (padId, text, authorId, time) => {
|
appendChatMessage: async (padId, text, authorId, time) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
if (typeof text !== 'string') throw new CustomError('text is not a string', 'apierror');
|
if (typeof text !== 'string') throw new CustomError('text is not a string', 'apierror');
|
||||||
if (time === undefined || !Number.isInteger(Number.parseFloat(time))) time = Date.now();
|
if (time === undefined || !Number.isInteger(Number.parseFloat(time))) time = Date.now();
|
||||||
await sendChatMessageToPadClients(new ChatMessage(text, authorId, time), padId);
|
await sendChatMessageToPadClients(new ChatMessage(text, authorId, time), padId);
|
||||||
|
@ -247,6 +266,9 @@ api.registerChatHandlers({
|
||||||
* {code: 1, message:"padID does not exist", data: null}
|
* {code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
getChatHead: async (padId) => {
|
getChatHead: async (padId) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
const pad = await getPadSafe(padId);
|
const pad = await getPadSafe(padId);
|
||||||
const {chatHead = -1} = pad;
|
const {chatHead = -1} = pad;
|
||||||
return {chatHead};
|
return {chatHead};
|
||||||
|
@ -267,6 +289,9 @@ api.registerChatHandlers({
|
||||||
* {code: 1, message:"padID does not exist", data: null}
|
* {code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
getChatHistory: async (padId, start, end) => {
|
getChatHistory: async (padId, start, end) => {
|
||||||
|
if (!settings.integratedChat) {
|
||||||
|
throw new Error('integrated chat is disabled (see integratedChat in settings.json)');
|
||||||
|
}
|
||||||
if (start && end) {
|
if (start && end) {
|
||||||
if (start < 0) throw new CustomError('start is below zero', 'apierror');
|
if (start < 0) throw new CustomError('start is below zero', 'apierror');
|
||||||
if (end < 0) throw new CustomError('end is below zero', 'apierror');
|
if (end < 0) throw new CustomError('end is below zero', 'apierror');
|
||||||
|
|
|
@ -28,11 +28,13 @@ const tar = (() => {
|
||||||
'pad_impexp.js',
|
'pad_impexp.js',
|
||||||
'pad_savedrevs.js',
|
'pad_savedrevs.js',
|
||||||
'pad_connectionstatus.js',
|
'pad_connectionstatus.js',
|
||||||
'ChatMessage.js',
|
...settings.integratedChat ? [
|
||||||
'chat.js',
|
'ChatMessage.js',
|
||||||
|
'chat.js',
|
||||||
|
'$tinycon/tinycon.js',
|
||||||
|
] : [],
|
||||||
'vendors/gritter.js',
|
'vendors/gritter.js',
|
||||||
'$js-cookie/dist/js.cookie.js',
|
'$js-cookie/dist/js.cookie.js',
|
||||||
'$tinycon/tinycon.js',
|
|
||||||
'vendors/farbtastic.js',
|
'vendors/farbtastic.js',
|
||||||
'skin_variants.js',
|
'skin_variants.js',
|
||||||
'socketio.js',
|
'socketio.js',
|
||||||
|
|
|
@ -38,8 +38,10 @@ exports.expressPreSession = async (hookName, {app}) => {
|
||||||
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
|
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
|
||||||
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
|
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
|
||||||
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
|
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
|
||||||
if (plugin === 'ep_etherpad-lite' && !settings.enableAdminUITests &&
|
if (plugin === 'ep_etherpad-lite') {
|
||||||
spec.startsWith('admin')) continue;
|
if (!settings.enableAdminUITests && spec.startsWith('admin')) continue;
|
||||||
|
if (!settings.integratedChat && spec.startsWith('chat')) continue;
|
||||||
|
}
|
||||||
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
|
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -42,7 +42,7 @@ const LIBRARY_WHITELIST = [
|
||||||
'js-cookie',
|
'js-cookie',
|
||||||
'security',
|
'security',
|
||||||
'split-grid',
|
'split-grid',
|
||||||
'tinycon',
|
...settings.integratedChat ? ['tinycon'] : [],
|
||||||
'underscore',
|
'underscore',
|
||||||
'unorm',
|
'unorm',
|
||||||
];
|
];
|
||||||
|
|
|
@ -156,6 +156,12 @@ exports.defaultPadText = [
|
||||||
'Etherpad on Github: https://github.com/ether/etherpad-lite',
|
'Etherpad on Github: https://github.com/ether/etherpad-lite',
|
||||||
].join('\n');
|
].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.integratedChat = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default Pad Settings for a user (Can be overridden by changing the setting
|
* The default Pad Settings for a user (Can be overridden by changing the setting
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,6 +5,7 @@ const hooks = require('./hooks');
|
||||||
const log4js = require('log4js');
|
const log4js = require('log4js');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const runCmd = require('../../../node/utils/run_cmd');
|
const runCmd = require('../../../node/utils/run_cmd');
|
||||||
|
const settings = require('../../../node/utils/Settings');
|
||||||
const tsort = require('./tsort');
|
const tsort = require('./tsort');
|
||||||
const pluginUtils = require('./shared');
|
const pluginUtils = require('./shared');
|
||||||
const defs = require('./plugin_defs');
|
const defs = require('./plugin_defs');
|
||||||
|
@ -136,6 +137,9 @@ const loadPlugin = async (packages, pluginName, plugins, parts) => {
|
||||||
const data = await fs.readFile(pluginPath);
|
const data = await fs.readFile(pluginPath);
|
||||||
try {
|
try {
|
||||||
const plugin = JSON.parse(data);
|
const plugin = JSON.parse(data);
|
||||||
|
if (pluginName === 'ep_etherpad-lite' && !settings.integratedChat) {
|
||||||
|
plugin.parts = plugin.parts.filter((part) => part.name !== 'chat');
|
||||||
|
}
|
||||||
plugin.package = packages[pluginName];
|
plugin.package = packages[pluginName];
|
||||||
plugins[pluginName] = plugin;
|
plugins[pluginName] = plugin;
|
||||||
for (const part of plugin.parts) {
|
for (const part of plugin.parts) {
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const common = require('../../common');
|
const common = require('../../common');
|
||||||
|
const plugins = require('../../../../static/js/pluginfw/plugins');
|
||||||
|
const settings = require('../../../../node/utils/Settings');
|
||||||
|
|
||||||
let agent;
|
let agent;
|
||||||
const apiKey = common.apiKey;
|
const apiKey = common.apiKey;
|
||||||
|
@ -13,7 +15,12 @@ const timestamp = Date.now();
|
||||||
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
|
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
|
||||||
|
|
||||||
describe(__filename, function () {
|
describe(__filename, function () {
|
||||||
|
const backups = {settings: {}};
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
|
backups.settings.integratedChat = settings.integratedChat;
|
||||||
|
settings.integratedChat = true;
|
||||||
|
await plugins.update();
|
||||||
agent = await common.init();
|
agent = await common.init();
|
||||||
await agent.get('/api/')
|
await agent.get('/api/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -37,7 +44,16 @@ describe(__filename, function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('message sequence', function () {
|
after(async function () {
|
||||||
|
Object.assign(settings, backups.settings);
|
||||||
|
await plugins.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('settings.integratedChat = true', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
settings.integratedChat = true;
|
||||||
|
});
|
||||||
|
|
||||||
it('appendChatMessage', async function () {
|
it('appendChatMessage', async function () {
|
||||||
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
|
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
|
||||||
`&authorID=${authorID}&time=${timestamp}`)
|
`&authorID=${authorID}&time=${timestamp}`)
|
||||||
|
@ -68,6 +84,40 @@ describe(__filename, function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('settings.integratedChat = false', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
settings.integratedChat = 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() {
|
function makeid() {
|
||||||
|
|
|
@ -6,29 +6,37 @@ const assert = require('assert').strict;
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
const padManager = require('../../../node/db/PadManager');
|
const padManager = require('../../../node/db/PadManager');
|
||||||
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
||||||
|
const plugins = require('../../../static/js/pluginfw/plugins');
|
||||||
|
const settings = require('../../../node/utils/Settings');
|
||||||
|
|
||||||
const logger = common.logger;
|
const logger = common.logger;
|
||||||
|
|
||||||
const checkHook = async (hookName, checkFn) => {
|
const checkHook = async (hookName, checkFn) => {
|
||||||
if (pluginDefs.hooks[hookName] == null) pluginDefs.hooks[hookName] = [];
|
if (pluginDefs.hooks[hookName] == null) pluginDefs.hooks[hookName] = [];
|
||||||
await new Promise((resolve, reject) => {
|
let hook;
|
||||||
pluginDefs.hooks[hookName].push({
|
try {
|
||||||
hook_fn: async (hookName, context) => {
|
await new Promise((resolve, reject) => {
|
||||||
if (checkFn == null) return;
|
hook = {
|
||||||
logger.debug(`hook ${hookName} invoked`);
|
hook_fn: async (hookName, context) => {
|
||||||
try {
|
if (checkFn == null) return;
|
||||||
// Make sure checkFn is called only once.
|
logger.debug(`hook ${hookName} invoked`);
|
||||||
const _checkFn = checkFn;
|
try {
|
||||||
checkFn = null;
|
// Make sure checkFn is called only once.
|
||||||
await _checkFn(context);
|
const _checkFn = checkFn;
|
||||||
} catch (err) {
|
checkFn = null;
|
||||||
reject(err);
|
await _checkFn(context);
|
||||||
return;
|
} catch (err) {
|
||||||
}
|
reject(err);
|
||||||
resolve();
|
return;
|
||||||
},
|
}
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
pluginDefs.hooks[hookName].push(hook);
|
||||||
});
|
});
|
||||||
});
|
} finally {
|
||||||
|
pluginDefs.hooks[hookName] = pluginDefs.hooks[hookName].filter((h) => h !== hook);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendMessage = async (socket, data) => (
|
const sendMessage = async (socket, data) => (
|
||||||
|
@ -37,119 +45,146 @@ const sendChat = async (socket, message) => (
|
||||||
await sendMessage(socket, {type: 'CHAT_MESSAGE', message}));
|
await sendMessage(socket, {type: 'CHAT_MESSAGE', message}));
|
||||||
|
|
||||||
describe(__filename, function () {
|
describe(__filename, function () {
|
||||||
|
const backups = {settings: {}};
|
||||||
|
let clientVars;
|
||||||
const padId = 'testChatPad';
|
const padId = 'testChatPad';
|
||||||
const hooksBackup = {};
|
let socket;
|
||||||
|
|
||||||
|
const connect = async () => {
|
||||||
|
socket = await common.connect();
|
||||||
|
({data: clientVars} = await common.handshake(socket, padId));
|
||||||
|
};
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
for (const [name, defs] of Object.entries(pluginDefs.hooks)) {
|
backups.settings.integratedChat = settings.integratedChat;
|
||||||
if (defs == null) continue;
|
|
||||||
hooksBackup[name] = defs;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
for (const [name, defs] of Object.entries(hooksBackup)) pluginDefs.hooks[name] = [...defs];
|
|
||||||
for (const name of Object.keys(pluginDefs.hooks)) {
|
|
||||||
if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
|
|
||||||
}
|
|
||||||
if (await padManager.doesPadExist(padId)) {
|
if (await padManager.doesPadExist(padId)) {
|
||||||
const pad = await padManager.getPad(padId);
|
const pad = await padManager.getPad(padId);
|
||||||
await pad.remove();
|
await pad.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async function () {
|
afterEach(async function () {
|
||||||
Object.assign(pluginDefs.hooks, hooksBackup);
|
if (socket) {
|
||||||
for (const name of Object.keys(pluginDefs.hooks)) {
|
socket.close();
|
||||||
if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
|
socket = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('chatNewMessage hook', function () {
|
after(async function () {
|
||||||
let authorId;
|
Object.assign(settings, backups.settings);
|
||||||
let socket;
|
await plugins.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('settings.integratedChat = true', function () {
|
||||||
|
before(async function () {
|
||||||
|
settings.integratedChat = true;
|
||||||
|
await plugins.update();
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
socket = await common.connect();
|
await connect();
|
||||||
const {data: clientVars} = await common.handshake(socket, padId);
|
|
||||||
authorId = clientVars.userId;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async function () {
|
describe('chatNewMessage hook', function () {
|
||||||
socket.close();
|
it('message', async function () {
|
||||||
});
|
const start = Date.now();
|
||||||
|
await Promise.all([
|
||||||
it('message', async function () {
|
checkHook('chatNewMessage', ({message}) => {
|
||||||
const start = Date.now();
|
assert(message != null);
|
||||||
await Promise.all([
|
assert(message instanceof ChatMessage);
|
||||||
checkHook('chatNewMessage', ({message}) => {
|
assert.equal(message.authorId, clientVars.userId);
|
||||||
assert(message != null);
|
assert.equal(message.text, this.test.title);
|
||||||
assert(message instanceof ChatMessage);
|
assert(message.time >= start);
|
||||||
assert.equal(message.authorId, authorId);
|
assert(message.time <= Date.now());
|
||||||
assert.equal(message.text, this.test.title);
|
}),
|
||||||
assert(message.time >= start);
|
sendChat(socket, {text: this.test.title}),
|
||||||
assert(message.time <= Date.now());
|
]);
|
||||||
}),
|
|
||||||
sendChat(socket, {text: this.test.title}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('pad', async function () {
|
|
||||||
await Promise.all([
|
|
||||||
checkHook('chatNewMessage', ({pad}) => {
|
|
||||||
assert(pad != null);
|
|
||||||
assert(pad instanceof Pad);
|
|
||||||
assert.equal(pad.id, padId);
|
|
||||||
}),
|
|
||||||
sendChat(socket, {text: this.test.title}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('padId', async function () {
|
|
||||||
await Promise.all([
|
|
||||||
checkHook('chatNewMessage', (context) => {
|
|
||||||
assert.equal(context.padId, padId);
|
|
||||||
}),
|
|
||||||
sendChat(socket, {text: this.test.title}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('mutations propagate', async function () {
|
|
||||||
const listen = async (type) => await new Promise((resolve) => {
|
|
||||||
const handler = (msg) => {
|
|
||||||
if (msg.type !== 'COLLABROOM') return;
|
|
||||||
if (msg.data == null || msg.data.type !== type) return;
|
|
||||||
resolve(msg.data);
|
|
||||||
socket.off('message', handler);
|
|
||||||
};
|
|
||||||
socket.on('message', handler);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const modifiedText = `${this.test.title} <added changes>`;
|
it('pad', async function () {
|
||||||
const customMetadata = {foo: this.test.title};
|
await Promise.all([
|
||||||
await Promise.all([
|
checkHook('chatNewMessage', ({pad}) => {
|
||||||
checkHook('chatNewMessage', ({message}) => {
|
assert(pad != null);
|
||||||
message.text = modifiedText;
|
assert(pad instanceof Pad);
|
||||||
message.customMetadata = customMetadata;
|
assert.equal(pad.id, padId);
|
||||||
}),
|
}),
|
||||||
(async () => {
|
sendChat(socket, {text: this.test.title}),
|
||||||
const {message} = await listen('CHAT_MESSAGE');
|
]);
|
||||||
assert(message != null);
|
});
|
||||||
assert.equal(message.text, modifiedText);
|
|
||||||
assert.deepEqual(message.customMetadata, customMetadata);
|
it('padId', async function () {
|
||||||
})(),
|
await Promise.all([
|
||||||
sendChat(socket, {text: this.test.title}),
|
checkHook('chatNewMessage', (context) => {
|
||||||
]);
|
assert.equal(context.padId, padId);
|
||||||
// Simulate fetch of historical chat messages when a pad is first loaded.
|
}),
|
||||||
await Promise.all([
|
sendChat(socket, {text: this.test.title}),
|
||||||
(async () => {
|
]);
|
||||||
const {messages: [message]} = await listen('CHAT_MESSAGES');
|
});
|
||||||
assert(message != null);
|
|
||||||
assert.equal(message.text, modifiedText);
|
it('mutations propagate', async function () {
|
||||||
assert.deepEqual(message.customMetadata, customMetadata);
|
const listen = async (type) => await new Promise((resolve) => {
|
||||||
})(),
|
const handler = (msg) => {
|
||||||
sendMessage(socket, {type: 'GET_CHAT_MESSAGES', start: 0, end: 0}),
|
if (msg.type !== 'COLLABROOM') return;
|
||||||
]);
|
if (msg.data == null || msg.data.type !== type) return;
|
||||||
|
resolve(msg.data);
|
||||||
|
socket.off('message', handler);
|
||||||
|
};
|
||||||
|
socket.on('message', handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
const modifiedText = `${this.test.title} <added changes>`;
|
||||||
|
const customMetadata = {foo: this.test.title};
|
||||||
|
await Promise.all([
|
||||||
|
checkHook('chatNewMessage', ({message}) => {
|
||||||
|
message.text = modifiedText;
|
||||||
|
message.customMetadata = customMetadata;
|
||||||
|
}),
|
||||||
|
(async () => {
|
||||||
|
const {message} = await listen('CHAT_MESSAGE');
|
||||||
|
assert(message != null);
|
||||||
|
assert.equal(message.text, modifiedText);
|
||||||
|
assert.deepEqual(message.customMetadata, customMetadata);
|
||||||
|
})(),
|
||||||
|
sendChat(socket, {text: this.test.title}),
|
||||||
|
]);
|
||||||
|
// Simulate fetch of historical chat messages when a pad is first loaded.
|
||||||
|
await Promise.all([
|
||||||
|
(async () => {
|
||||||
|
const {messages: [message]} = await listen('CHAT_MESSAGES');
|
||||||
|
assert(message != null);
|
||||||
|
assert.equal(message.text, modifiedText);
|
||||||
|
assert.deepEqual(message.customMetadata, customMetadata);
|
||||||
|
})(),
|
||||||
|
sendMessage(socket, {type: 'GET_CHAT_MESSAGES', start: 0, end: 0}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('settings.integratedChat = false', function () {
|
||||||
|
before(async function () {
|
||||||
|
settings.integratedChat = false;
|
||||||
|
await plugins.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
await connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clientVars.chatHead is unset', async function () {
|
||||||
|
assert(!('chatHead' in clientVars), `chatHead should be unset, is ${clientVars.chatHead}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects CHAT_MESSAGE messages', async function () {
|
||||||
|
await assert.rejects(sendChat(socket, {text: 'this is a test'}), /unknown message type/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects GET_CHAT_MESSAGES messages', async function () {
|
||||||
|
const msg = {type: 'GET_CHAT_MESSAGES', start: 0, end: 0};
|
||||||
|
await assert.rejects(sendMessage(socket, msg), /unknown message type/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue