mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
Added typescript support for most backend files.
This commit is contained in:
parent
d6abab6c74
commit
331cf3d79f
46 changed files with 19975 additions and 7995 deletions
1
src/.gitignore
vendored
Normal file
1
src/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
dist
|
|
@ -19,59 +19,72 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Changeset = require('../../static/js/Changeset');
|
import Changeset from '../../static/js/Changeset';
|
||||||
const ChatMessage = require('../../static/js/ChatMessage');
|
import ChatMessage from '../../static/js/ChatMessage';
|
||||||
const CustomError = require('../utils/customError');
|
import CustomError from '../utils/customError';
|
||||||
const padManager = require('./PadManager');
|
import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager';
|
||||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
import {
|
||||||
const readOnlyManager = require('./ReadOnlyManager');
|
handleCustomMessage,
|
||||||
const groupManager = require('./GroupManager');
|
sendChatMessageToPadClients,
|
||||||
const authorManager = require('./AuthorManager');
|
sessioninfos,
|
||||||
const sessionManager = require('./SessionManager');
|
updatePadClients
|
||||||
const exportHtml = require('../utils/ExportHtml');
|
} from '../handler/PadMessageHandler';
|
||||||
const exportTxt = require('../utils/ExportTxt');
|
import {getPadId, getReadOnlyId} from './ReadOnlyManager';
|
||||||
const importHtml = require('../utils/ImportHtml');
|
import {
|
||||||
|
createGroup,
|
||||||
|
createGroupIfNotExistsFor,
|
||||||
|
createGroupPad,
|
||||||
|
deleteGroup,
|
||||||
|
listAllGroups,
|
||||||
|
listPads
|
||||||
|
} from './GroupManager';
|
||||||
|
import {createAuthor, createAuthorIfNotExistsFor, getAuthorName, listPadsOfAuthor} from './AuthorManager';
|
||||||
|
import {} from './SessionManager';
|
||||||
|
import exportHtml from '../utils/ExportHtml';
|
||||||
|
import exportTxt from '../utils/ExportTxt';
|
||||||
|
import importHtml from '../utils/ImportHtml';
|
||||||
const cleanText = require('./Pad').cleanText;
|
const cleanText = require('./Pad').cleanText;
|
||||||
const PadDiff = require('../utils/padDiff');
|
import PadDiff from '../utils/padDiff';
|
||||||
|
|
||||||
/* ********************
|
/* ********************
|
||||||
* GROUP FUNCTIONS ****
|
* GROUP FUNCTIONS ****
|
||||||
******************** */
|
******************** */
|
||||||
|
|
||||||
exports.listAllGroups = groupManager.listAllGroups;
|
/*
|
||||||
exports.createGroup = groupManager.createGroup;
|
exports.listAllGroups = listAllGroups;
|
||||||
exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
|
exports.createGroup = createGroup;
|
||||||
exports.deleteGroup = groupManager.deleteGroup;
|
exports.createGroupIfNotExistsFor = createGroupIfNotExistsFor;
|
||||||
exports.listPads = groupManager.listPads;
|
exports.deleteGroup = deleteGroup;
|
||||||
exports.createGroupPad = groupManager.createGroupPad;
|
exports.listPads = listPads;
|
||||||
|
exports.createGroupPad = createGroupPad;
|
||||||
|
*/
|
||||||
/* ********************
|
/* ********************
|
||||||
* PADLIST FUNCTION ***
|
* PADLIST FUNCTION ***
|
||||||
******************** */
|
******************** */
|
||||||
|
/*
|
||||||
exports.listAllPads = padManager.listAllPads;
|
exports.listAllPads = padManager.listAllPads;
|
||||||
|
*/
|
||||||
/* ********************
|
/* ********************
|
||||||
* AUTHOR FUNCTIONS ***
|
* AUTHOR FUNCTIONS ***
|
||||||
******************** */
|
******************** */
|
||||||
|
/*
|
||||||
exports.createAuthor = authorManager.createAuthor;
|
exports.createAuthor = createAuthor;
|
||||||
exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
|
exports.createAuthorIfNotExistsFor = createAuthorIfNotExistsFor;
|
||||||
exports.getAuthorName = authorManager.getAuthorName;
|
exports.getAuthorName = getAuthorName;
|
||||||
exports.listPadsOfAuthor = authorManager.listPadsOfAuthor;
|
exports.listPadsOfAuthor = listPadsOfAuthor;
|
||||||
exports.padUsers = padMessageHandler.padUsers;
|
exports.padUsers = padMessageHandler.padUsers;
|
||||||
exports.padUsersCount = padMessageHandler.padUsersCount;
|
exports.padUsersCount = padMessageHandler.padUsersCount;
|
||||||
|
*/
|
||||||
/* ********************
|
/* ********************
|
||||||
* SESSION FUNCTIONS **
|
* SESSION FUNCTIONS **
|
||||||
******************** */
|
******************** */
|
||||||
|
/*
|
||||||
exports.createSession = sessionManager.createSession;
|
exports.createSession = sessionManager.createSession;
|
||||||
exports.deleteSession = sessionManager.deleteSession;
|
exports.deleteSession = sessionManager.deleteSession;
|
||||||
exports.getSessionInfo = sessionManager.getSessionInfo;
|
exports.getSessionInfo = sessionManager.getSessionInfo;
|
||||||
exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
|
exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
|
||||||
exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
|
exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
|
||||||
|
*/
|
||||||
/* ***********************
|
/* ***********************
|
||||||
* PAD CONTENT FUNCTIONS *
|
* PAD CONTENT FUNCTIONS *
|
||||||
*********************** */
|
*********************** */
|
||||||
|
@ -103,7 +116,7 @@ Example returns:
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
exports.getAttributePool = async (padID) => {
|
export const getAttributePool = async (padID: string) => {
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
return {pool: pad.pool};
|
return {pool: pad.pool};
|
||||||
};
|
};
|
||||||
|
@ -121,7 +134,7 @@ Example returns:
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
exports.getRevisionChangeset = async (padID, rev) => {
|
export const getRevisionChangeset = async (padID, rev) => {
|
||||||
// try to parse the revision number
|
// try to parse the revision number
|
||||||
if (rev !== undefined) {
|
if (rev !== undefined) {
|
||||||
rev = checkValidRev(rev);
|
rev = checkValidRev(rev);
|
||||||
|
@ -154,7 +167,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {text:"Welcome Text"}}
|
{code: 0, message:"ok", data: {text:"Welcome Text"}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getText = async (padID, rev) => {
|
export const getText = async (padID, rev) => {
|
||||||
// try to parse the revision number
|
// try to parse the revision number
|
||||||
if (rev !== undefined) {
|
if (rev !== undefined) {
|
||||||
rev = checkValidRev(rev);
|
rev = checkValidRev(rev);
|
||||||
|
@ -192,7 +205,7 @@ Example returns:
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
{code: 1, message:"text too long", data: null}
|
{code: 1, message:"text too long", data: null}
|
||||||
*/
|
*/
|
||||||
exports.setText = async (padID, text, authorId = '') => {
|
export const setText = async (padID, text, authorId = '') => {
|
||||||
// text is required
|
// text is required
|
||||||
if (typeof text !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
throw new CustomError('text is not a string', 'apierror');
|
throw new CustomError('text is not a string', 'apierror');
|
||||||
|
@ -202,7 +215,7 @@ exports.setText = async (padID, text, authorId = '') => {
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
|
|
||||||
await pad.setText(text, authorId);
|
await pad.setText(text, authorId);
|
||||||
await padMessageHandler.updatePadClients(pad);
|
await updatePadClients(pad);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,7 +227,7 @@ Example returns:
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
{code: 1, message:"text too long", data: null}
|
{code: 1, message:"text too long", data: null}
|
||||||
*/
|
*/
|
||||||
exports.appendText = async (padID, text, authorId = '') => {
|
export const appendText = async (padID, text, authorId = '') => {
|
||||||
// text is required
|
// text is required
|
||||||
if (typeof text !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
throw new CustomError('text is not a string', 'apierror');
|
throw new CustomError('text is not a string', 'apierror');
|
||||||
|
@ -222,7 +235,7 @@ exports.appendText = async (padID, text, authorId = '') => {
|
||||||
|
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
await pad.appendText(text, authorId);
|
await pad.appendText(text, authorId);
|
||||||
await padMessageHandler.updatePadClients(pad);
|
await updatePadClients(pad);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,7 +246,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
|
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getHTML = async (padID, rev) => {
|
export const getHTML = async (padID, rev) => {
|
||||||
if (rev !== undefined) {
|
if (rev !== undefined) {
|
||||||
rev = checkValidRev(rev);
|
rev = checkValidRev(rev);
|
||||||
}
|
}
|
||||||
|
@ -265,7 +278,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.setHTML = async (padID, html, authorId = '') => {
|
export const setHTML = async (padID, html, authorId = '') => {
|
||||||
// html string is required
|
// html string is required
|
||||||
if (typeof html !== 'string') {
|
if (typeof html !== 'string') {
|
||||||
throw new CustomError('html is not a string', 'apierror');
|
throw new CustomError('html is not a string', 'apierror');
|
||||||
|
@ -282,7 +295,7 @@ exports.setHTML = async (padID, html, authorId = '') => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the clients on the pad
|
// update the clients on the pad
|
||||||
padMessageHandler.updatePadClients(pad);
|
updatePadClients(pad);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ****************
|
/* ****************
|
||||||
|
@ -303,7 +316,7 @@ Example returns:
|
||||||
|
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getChatHistory = async (padID, start, end) => {
|
export const getChatHistory = async (padID, start, end) => {
|
||||||
if (start && end) {
|
if (start && end) {
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
throw new CustomError('start is below zero', 'apierror');
|
throw new CustomError('start is below zero', 'apierror');
|
||||||
|
@ -349,7 +362,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.appendChatMessage = async (padID, text, authorID, time) => {
|
export const appendChatMessage = async (padID, text, authorID, time) => {
|
||||||
// text is required
|
// text is required
|
||||||
if (typeof text !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
throw new CustomError('text is not a string', 'apierror');
|
throw new CustomError('text is not a string', 'apierror');
|
||||||
|
@ -363,7 +376,7 @@ exports.appendChatMessage = async (padID, text, authorID, time) => {
|
||||||
// @TODO - missing getPadSafe() call ?
|
// @TODO - missing getPadSafe() call ?
|
||||||
|
|
||||||
// save chat message to database and send message to all connected clients
|
// save chat message to database and send message to all connected clients
|
||||||
await padMessageHandler.sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
|
await sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ***************
|
/* ***************
|
||||||
|
@ -378,7 +391,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {revisions: 56}}
|
{code: 0, message:"ok", data: {revisions: 56}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getRevisionsCount = async (padID) => {
|
export const getRevisionsCount = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
return {revisions: pad.getHeadRevisionNumber()};
|
return {revisions: pad.getHeadRevisionNumber()};
|
||||||
|
@ -392,7 +405,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {savedRevisions: 42}}
|
{code: 0, message:"ok", data: {savedRevisions: 42}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getSavedRevisionsCount = async (padID) => {
|
export const getSavedRevisionsCount = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
return {savedRevisions: pad.getSavedRevisionsNumber()};
|
return {savedRevisions: pad.getSavedRevisionsNumber()};
|
||||||
|
@ -406,7 +419,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
|
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.listSavedRevisions = async (padID) => {
|
export const listSavedRevisions = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
return {savedRevisions: pad.getSavedRevisionsList()};
|
return {savedRevisions: pad.getSavedRevisionsList()};
|
||||||
|
@ -420,7 +433,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.saveRevision = async (padID, rev) => {
|
export const saveRevision = async (padID, rev) => {
|
||||||
// check if rev is a number
|
// check if rev is a number
|
||||||
if (rev !== undefined) {
|
if (rev !== undefined) {
|
||||||
rev = checkValidRev(rev);
|
rev = checkValidRev(rev);
|
||||||
|
@ -439,7 +452,7 @@ exports.saveRevision = async (padID, rev) => {
|
||||||
rev = pad.getHeadRevisionNumber();
|
rev = pad.getHeadRevisionNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
const author = await authorManager.createAuthor('API');
|
const author = await createAuthor('API');
|
||||||
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
|
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -451,7 +464,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
|
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getLastEdited = async (padID) => {
|
export const getLastEdited = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
const lastEdited = await pad.getLastEdit();
|
const lastEdited = await pad.getLastEdit();
|
||||||
|
@ -466,7 +479,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"pad does already exist", data: null}
|
{code: 1, message:"pad does already exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.createPad = async (padID, text, authorId = '') => {
|
export const createPad = async (padID, text, authorId = '') => {
|
||||||
if (padID) {
|
if (padID) {
|
||||||
// ensure there is no $ in the padID
|
// ensure there is no $ in the padID
|
||||||
if (padID.indexOf('$') !== -1) {
|
if (padID.indexOf('$') !== -1) {
|
||||||
|
@ -491,7 +504,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.deletePad = async (padID) => {
|
export const deletePad = async (padID) => {
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
await pad.remove();
|
await pad.remove();
|
||||||
};
|
};
|
||||||
|
@ -504,7 +517,7 @@ exports.deletePad = async (padID) => {
|
||||||
{code:0, message:"ok", data:null}
|
{code:0, message:"ok", data:null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.restoreRevision = async (padID, rev, authorId = '') => {
|
export const restoreRevision = async (padID, rev, authorId = '') => {
|
||||||
// check if rev is a number
|
// check if rev is a number
|
||||||
if (rev === undefined) {
|
if (rev === undefined) {
|
||||||
throw new CustomError('rev is not defined', 'apierror');
|
throw new CustomError('rev is not defined', 'apierror');
|
||||||
|
@ -556,7 +569,7 @@ exports.restoreRevision = async (padID, rev, authorId = '') => {
|
||||||
const changeset = builder.toString();
|
const changeset = builder.toString();
|
||||||
|
|
||||||
await pad.appendRevision(changeset, authorId);
|
await pad.appendRevision(changeset, authorId);
|
||||||
await padMessageHandler.updatePadClients(pad);
|
await updatePadClients(pad);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -568,7 +581,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.copyPad = async (sourceID, destinationID, force) => {
|
export const copyPad = async (sourceID, destinationID, force) => {
|
||||||
const pad = await getPadSafe(sourceID, true);
|
const pad = await getPadSafe(sourceID, true);
|
||||||
await pad.copy(destinationID, force);
|
await pad.copy(destinationID, force);
|
||||||
};
|
};
|
||||||
|
@ -582,7 +595,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
|
export const copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
|
||||||
const pad = await getPadSafe(sourceID, true);
|
const pad = await getPadSafe(sourceID, true);
|
||||||
await pad.copyPadWithoutHistory(destinationID, force, authorId);
|
await pad.copyPadWithoutHistory(destinationID, force, authorId);
|
||||||
};
|
};
|
||||||
|
@ -596,7 +609,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {padID: destinationID}}
|
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.movePad = async (sourceID, destinationID, force) => {
|
export const movePad = async (sourceID, destinationID, force) => {
|
||||||
const pad = await getPadSafe(sourceID, true);
|
const pad = await getPadSafe(sourceID, true);
|
||||||
await pad.copy(destinationID, force);
|
await pad.copy(destinationID, force);
|
||||||
await pad.remove();
|
await pad.remove();
|
||||||
|
@ -610,12 +623,12 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getReadOnlyID = async (padID) => {
|
export const getReadOnlyID = async (padID) => {
|
||||||
// we don't need the pad object, but this function does all the security stuff for us
|
// we don't need the pad object, but this function does all the security stuff for us
|
||||||
await getPadSafe(padID, true);
|
await getPadSafe(padID, true);
|
||||||
|
|
||||||
// get the readonlyId
|
// get the readonlyId
|
||||||
const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
|
const readOnlyID = await getReadOnlyId(padID);
|
||||||
|
|
||||||
return {readOnlyID};
|
return {readOnlyID};
|
||||||
};
|
};
|
||||||
|
@ -628,9 +641,9 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {padID: padID}}
|
{code: 0, message:"ok", data: {padID: padID}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getPadID = async (roID) => {
|
export const getPadID = async (roID) => {
|
||||||
// get the PadId
|
// get the PadId
|
||||||
const padID = await readOnlyManager.getPadId(roID);
|
const padID = await getPadId(roID);
|
||||||
if (padID == null) {
|
if (padID == null) {
|
||||||
throw new CustomError('padID does not exist', 'apierror');
|
throw new CustomError('padID does not exist', 'apierror');
|
||||||
}
|
}
|
||||||
|
@ -646,7 +659,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: null}
|
{code: 0, message:"ok", data: null}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.setPublicStatus = async (padID, publicStatus) => {
|
export const setPublicStatus = async (padID, publicStatus) => {
|
||||||
// ensure this is a group pad
|
// ensure this is a group pad
|
||||||
checkGroupPad(padID, 'publicStatus');
|
checkGroupPad(padID, 'publicStatus');
|
||||||
|
|
||||||
|
@ -669,7 +682,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {publicStatus: true}}
|
{code: 0, message:"ok", data: {publicStatus: true}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getPublicStatus = async (padID) => {
|
export const getPublicStatus = async (padID) => {
|
||||||
// ensure this is a group pad
|
// ensure this is a group pad
|
||||||
checkGroupPad(padID, 'publicStatus');
|
checkGroupPad(padID, 'publicStatus');
|
||||||
|
|
||||||
|
@ -686,7 +699,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
|
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.listAuthorsOfPad = async (padID) => {
|
export const listAuthorsOfPad = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
const authorIDs = pad.getAllAuthors();
|
const authorIDs = pad.getAllAuthors();
|
||||||
|
@ -716,9 +729,9 @@ Example returns:
|
||||||
{code: 1, message:"padID does not exist"}
|
{code: 1, message:"padID does not exist"}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.sendClientsMessage = async (padID, msg) => {
|
export const sendClientsMessage = async (padID, msg) => {
|
||||||
await getPadSafe(padID, true); // Throw if the padID is invalid or if the pad does not exist.
|
await getPadSafe(padID, true); // Throw if the padID is invalid or if the pad does not exist.
|
||||||
padMessageHandler.handleCustomMessage(padID, msg);
|
handleCustomMessage(padID, msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -740,7 +753,7 @@ Example returns:
|
||||||
{code: 0, message:"ok", data: {chatHead: 42}}
|
{code: 0, message:"ok", data: {chatHead: 42}}
|
||||||
{code: 1, message:"padID does not exist", data: null}
|
{code: 1, message:"padID does not exist", data: null}
|
||||||
*/
|
*/
|
||||||
exports.getChatHead = async (padID) => {
|
export const getChatHead = async (padID) => {
|
||||||
// get the pad
|
// get the pad
|
||||||
const pad = await getPadSafe(padID, true);
|
const pad = await getPadSafe(padID, true);
|
||||||
return {chatHead: pad.chatHead};
|
return {chatHead: pad.chatHead};
|
||||||
|
@ -764,7 +777,7 @@ Example returns:
|
||||||
{"code":4,"message":"no or wrong API Key","data":null}
|
{"code":4,"message":"no or wrong API Key","data":null}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
exports.createDiffHTML = async (padID, startRev, endRev) => {
|
export const createDiffHTML = async (padID, startRev, endRev) => {
|
||||||
// check if startRev is a number
|
// check if startRev is a number
|
||||||
if (startRev !== undefined) {
|
if (startRev !== undefined) {
|
||||||
startRev = checkValidRev(startRev);
|
startRev = checkValidRev(startRev);
|
||||||
|
@ -803,13 +816,13 @@ exports.createDiffHTML = async (padID, startRev, endRev) => {
|
||||||
{"code":4,"message":"no or wrong API Key","data":null}
|
{"code":4,"message":"no or wrong API Key","data":null}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.getStats = async () => {
|
export const getStats = async () => {
|
||||||
const sessionInfos = padMessageHandler.sessioninfos;
|
const sessionInfos = sessioninfos;
|
||||||
|
|
||||||
const sessionKeys = Object.keys(sessionInfos);
|
const sessionKeys = Object.keys(sessionInfos);
|
||||||
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
|
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
|
||||||
|
|
||||||
const {padIDs} = await padManager.listAllPads();
|
const {padIDs} = await listAllPads();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalPads: padIDs.length,
|
totalPads: padIDs.length,
|
||||||
|
@ -826,19 +839,19 @@ exports.getStats = async () => {
|
||||||
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
||||||
|
|
||||||
// gets a pad safe
|
// gets a pad safe
|
||||||
const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
const getPadSafe = async (padID, shouldExist, text?, authorId = '') => {
|
||||||
// check if padID is a string
|
// check if padID is a string
|
||||||
if (typeof padID !== 'string') {
|
if (typeof padID !== 'string') {
|
||||||
throw new CustomError('padID is not a string', 'apierror');
|
throw new CustomError('padID is not a string', 'apierror');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the padID maches the requirements
|
// check if the padID maches the requirements
|
||||||
if (!padManager.isValidPadId(padID)) {
|
if (!isValidPadId(padID)) {
|
||||||
throw new CustomError('padID did not match requirements', 'apierror');
|
throw new CustomError('padID did not match requirements', 'apierror');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the pad exists
|
// check if the pad exists
|
||||||
const exists = await padManager.doesPadExists(padID);
|
const exists = await doesPadExist(padID);
|
||||||
|
|
||||||
if (!exists && shouldExist) {
|
if (!exists && shouldExist) {
|
||||||
// does not exist, but should
|
// does not exist, but should
|
||||||
|
@ -851,7 +864,7 @@ const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pad exists, let's get it
|
// pad exists, let's get it
|
||||||
return padManager.getPad(padID, text, authorId);
|
return getPad(padID, text, authorId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// checks if a rev is a legal number
|
// checks if a rev is a legal number
|
|
@ -19,12 +19,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const db = require('./DB');
|
import {db} from './DB';
|
||||||
const CustomError = require('../utils/customError');
|
import CustomError from '../utils/customError';
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||||
|
|
||||||
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
|
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
|
||||||
|
|
||||||
exports.getColorPalette = () => [
|
export const getColorPalette = () => [
|
||||||
'#ffc7c7',
|
'#ffc7c7',
|
||||||
'#fff1c7',
|
'#fff1c7',
|
||||||
'#e3ffc7',
|
'#e3ffc7',
|
||||||
|
@ -94,26 +95,23 @@ exports.getColorPalette = () => [
|
||||||
/**
|
/**
|
||||||
* Checks if the author exists
|
* Checks if the author exists
|
||||||
*/
|
*/
|
||||||
exports.doesAuthorExist = async (authorID) => {
|
export const doesAuthorExist = async (authorID: string) => {
|
||||||
const author = await db.get(`globalAuthor:${authorID}`);
|
const author = await db.get(`globalAuthor:${authorID}`);
|
||||||
|
|
||||||
return author != null;
|
return author != null;
|
||||||
};
|
}
|
||||||
|
|
||||||
/* exported for backwards compatibility */
|
const getAuthor4Token2 = async (token: string) => {
|
||||||
exports.doesAuthorExists = exports.doesAuthorExist;
|
|
||||||
|
|
||||||
const getAuthor4Token = async (token) => {
|
|
||||||
const author = await mapAuthorWithDBKey('token2author', token);
|
const author = await mapAuthorWithDBKey('token2author', token);
|
||||||
|
|
||||||
// return only the sub value authorID
|
// return only the sub value authorID
|
||||||
return author ? author.authorID : author;
|
return author ? author.authorID : author;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getAuthorId = async (token, user) => {
|
export const getAuthorId = async (token, user) => {
|
||||||
const context = {dbKey: token, token, user};
|
const context = {dbKey: token, token, user};
|
||||||
let [authorId] = await hooks.aCallFirst('getAuthorId', context);
|
let [authorId] = await hooks.aCallFirst('getAuthorId', context);
|
||||||
if (!authorId) authorId = await getAuthor4Token(context.dbKey);
|
if (!authorId) authorId = await getAuthor4Token2(context.dbKey);
|
||||||
return authorId;
|
return authorId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,18 +121,18 @@ exports.getAuthorId = async (token, user) => {
|
||||||
* @deprecated Use `getAuthorId` instead.
|
* @deprecated Use `getAuthorId` instead.
|
||||||
* @param {String} token The token
|
* @param {String} token The token
|
||||||
*/
|
*/
|
||||||
exports.getAuthor4Token = async (token) => {
|
export const getAuthor4Token = async (token) => {
|
||||||
warnDeprecated(
|
warnDeprecated(
|
||||||
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
|
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
|
||||||
return await getAuthor4Token(token);
|
return await getAuthor4Token2(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the AuthorID for a mapper.
|
* Returns the AuthorID for a mapper.
|
||||||
* @param {String} token The mapper
|
* @param authorMapper
|
||||||
* @param {String} name The name of the author (optional)
|
* @param {String} name The name of the author (optional)
|
||||||
*/
|
*/
|
||||||
exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
|
export const createAuthorIfNotExistsFor = async (authorMapper, name: string) => {
|
||||||
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
|
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
|
@ -151,7 +149,7 @@ exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
|
||||||
* @param {String} mapperkey The database key name for this mapper
|
* @param {String} mapperkey The database key name for this mapper
|
||||||
* @param {String} mapper The mapper
|
* @param {String} mapper The mapper
|
||||||
*/
|
*/
|
||||||
const mapAuthorWithDBKey = async (mapperkey, mapper) => {
|
export const mapAuthorWithDBKey = async (mapperkey: string, mapper) => {
|
||||||
// try to map to an author
|
// try to map to an author
|
||||||
const author = await db.get(`${mapperkey}:${mapper}`);
|
const author = await db.get(`${mapperkey}:${mapper}`);
|
||||||
|
|
||||||
|
@ -178,7 +176,7 @@ const mapAuthorWithDBKey = async (mapperkey, mapper) => {
|
||||||
* Internal function that creates the database entry for an author
|
* Internal function that creates the database entry for an author
|
||||||
* @param {String} name The name of the author
|
* @param {String} name The name of the author
|
||||||
*/
|
*/
|
||||||
exports.createAuthor = async (name) => {
|
export const createAuthor = async (name) => {
|
||||||
// create the new author name
|
// create the new author name
|
||||||
const author = `a.${randomString(16)}`;
|
const author = `a.${randomString(16)}`;
|
||||||
|
|
||||||
|
@ -199,41 +197,41 @@ exports.createAuthor = async (name) => {
|
||||||
* Returns the Author Obj of the author
|
* Returns the Author Obj of the author
|
||||||
* @param {String} author The id of the author
|
* @param {String} author The id of the author
|
||||||
*/
|
*/
|
||||||
exports.getAuthor = async (author) => await db.get(`globalAuthor:${author}`);
|
export const getAuthor = async (author: string) => await db.get(`globalAuthor:${author}`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the color Id of the author
|
* Returns the color Id of the author
|
||||||
* @param {String} author The id of the author
|
* @param {String} author The id of the author
|
||||||
*/
|
*/
|
||||||
exports.getAuthorColorId = async (author) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
|
export const getAuthorColorId = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the color Id of the author
|
* Sets the color Id of the author
|
||||||
* @param {String} author The id of the author
|
* @param {String} author The id of the author
|
||||||
* @param {String} colorId The color id of the author
|
* @param {String} colorId The color id of the author
|
||||||
*/
|
*/
|
||||||
exports.setAuthorColorId = async (author, colorId) => await db.setSub(
|
export const setAuthorColorId = async (author: string, colorId: string) => await db.setSub(
|
||||||
`globalAuthor:${author}`, ['colorId'], colorId);
|
`globalAuthor:${author}`, ['colorId'], colorId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the author
|
* Returns the name of the author
|
||||||
* @param {String} author The id of the author
|
* @param {String} author The id of the author
|
||||||
*/
|
*/
|
||||||
exports.getAuthorName = async (author) => await db.getSub(`globalAuthor:${author}`, ['name']);
|
export const getAuthorName = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['name']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the name of the author
|
* Sets the name of the author
|
||||||
* @param {String} author The id of the author
|
* @param {String} author The id of the author
|
||||||
* @param {String} name The name of the author
|
* @param {String} name The name of the author
|
||||||
*/
|
*/
|
||||||
exports.setAuthorName = async (author, name) => await db.setSub(
|
export const setAuthorName = async (author: string, name:string) => await db.setSub(
|
||||||
`globalAuthor:${author}`, ['name'], name);
|
`globalAuthor:${author}`, ['name'], name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of all pads this author contributed to
|
* Returns an array of all pads this author contributed to
|
||||||
* @param {String} author The id of the author
|
* @param {String} authorID The id of the author
|
||||||
*/
|
*/
|
||||||
exports.listPadsOfAuthor = async (authorID) => {
|
export const listPadsOfAuthor = async (authorID:string) => {
|
||||||
/* There are two other places where this array is manipulated:
|
/* There are two other places where this array is manipulated:
|
||||||
* (1) When the author is added to a pad, the author object is also updated
|
* (1) When the author is added to a pad, the author object is also updated
|
||||||
* (2) When a pad is deleted, each author of that pad is also updated
|
* (2) When a pad is deleted, each author of that pad is also updated
|
||||||
|
@ -255,10 +253,10 @@ exports.listPadsOfAuthor = async (authorID) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new pad to the list of contributions
|
* Adds a new pad to the list of contributions
|
||||||
* @param {String} author The id of the author
|
* @param {String} authorID The id of the author
|
||||||
* @param {String} padID The id of the pad the author contributes to
|
* @param {String} padID The id of the pad the author contributes to
|
||||||
*/
|
*/
|
||||||
exports.addPad = async (authorID, padID) => {
|
export const addPad = async (authorID: string, padID: string) => {
|
||||||
// get the entry
|
// get the entry
|
||||||
const author = await db.get(`globalAuthor:${authorID}`);
|
const author = await db.get(`globalAuthor:${authorID}`);
|
||||||
|
|
||||||
|
@ -282,10 +280,10 @@ exports.addPad = async (authorID, padID) => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a pad from the list of contributions
|
* Removes a pad from the list of contributions
|
||||||
* @param {String} author The id of the author
|
* @param {String} authorID The id of the author
|
||||||
* @param {String} padID The id of the pad the author contributes to
|
* @param {String} padID The id of the pad the author contributes to
|
||||||
*/
|
*/
|
||||||
exports.removePad = async (authorID, padID) => {
|
export const removePad = async (authorID: string, padID: string) => {
|
||||||
const author = await db.get(`globalAuthor:${authorID}`);
|
const author = await db.get(`globalAuthor:${authorID}`);
|
||||||
|
|
||||||
if (author == null) return;
|
if (author == null) return;
|
|
@ -21,28 +21,29 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ueberDB = require('ueberdb2');
|
import ueberDB from 'ueberdb2';
|
||||||
const settings = require('../utils/Settings');
|
import {dbSettings, dbType} from '../utils/Settings';
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const stats = require('../stats');
|
import {shutdown as statsShutdown,createCollection} from '../stats';
|
||||||
|
import {} from 'measured-core'
|
||||||
const logger = log4js.getLogger('ueberDB');
|
const logger = log4js.getLogger('ueberDB');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UeberDB Object that provides the database functions
|
* The UeberDB Object that provides the database functions
|
||||||
*/
|
*/
|
||||||
exports.db = null;
|
const db = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the database with the settings provided by the settings module
|
* Initializes the database with the settings provided by the settings module
|
||||||
*/
|
*/
|
||||||
exports.init = async () => {
|
const init = async () => {
|
||||||
exports.db = new ueberDB.Database(settings.dbType, settings.dbSettings, null, logger);
|
exports.db = new ueberDB.Database(dbType, dbSettings, null, logger);
|
||||||
await exports.db.init();
|
await exports.db.init();
|
||||||
if (exports.db.metrics != null) {
|
if (exports.db.metrics != null) {
|
||||||
for (const [metric, value] of Object.entries(exports.db.metrics)) {
|
for (const [metric, value] of Object.entries(exports.db.metrics)) {
|
||||||
if (typeof value !== 'number') continue;
|
if (typeof value !== 'number') continue;
|
||||||
stats.gauge(`ueberdb_${metric}`, () => exports.db.metrics[metric]);
|
// FIXME find a better replacement for measure-core
|
||||||
|
createCollection.gauge(`ueberdb_${metric}`, () => exports.db.metrics[metric]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
|
for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
|
||||||
|
@ -53,8 +54,10 @@ exports.init = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.shutdown = async (hookName, context) => {
|
const shutdown = async (hookName, context) => {
|
||||||
if (exports.db != null) await exports.db.close();
|
if (exports.db != null) await exports.db.close();
|
||||||
exports.db = null;
|
exports.db = null;
|
||||||
logger.log('Database closed');
|
logger.log('Database closed');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export {db,init,shutdown}
|
|
@ -19,13 +19,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CustomError = require('../utils/customError');
|
import CustomError from '../utils/customError';
|
||||||
const randomString = require('../../static/js/pad_utils').randomString;
|
const randomString = require('../../static/js/pad_utils').randomString;
|
||||||
const db = require('./DB');
|
import {db} from './DB';
|
||||||
const padManager = require('./PadManager');
|
import {doesPadExist, getPad} from './PadManager';
|
||||||
const sessionManager = require('./SessionManager');
|
import {deleteSession} from './SessionManager';
|
||||||
|
|
||||||
exports.listAllGroups = async () => {
|
export const listAllGroups = async () => {
|
||||||
let groups = await db.get('groups');
|
let groups = await db.get('groups');
|
||||||
groups = groups || {};
|
groups = groups || {};
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ exports.listAllGroups = async () => {
|
||||||
return {groupIDs};
|
return {groupIDs};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.deleteGroup = async (groupID) => {
|
export const deleteGroup = async (groupID) => {
|
||||||
const group = await db.get(`group:${groupID}`);
|
const group = await db.get(`group:${groupID}`);
|
||||||
|
|
||||||
// ensure group exists
|
// ensure group exists
|
||||||
|
@ -44,7 +44,7 @@ exports.deleteGroup = async (groupID) => {
|
||||||
|
|
||||||
// iterate through all pads of this group and delete them (in parallel)
|
// iterate through all pads of this group and delete them (in parallel)
|
||||||
await Promise.all(Object.keys(group.pads).map(async (padId) => {
|
await Promise.all(Object.keys(group.pads).map(async (padId) => {
|
||||||
const pad = await padManager.getPad(padId);
|
const pad = await getPad(padId);
|
||||||
await pad.remove();
|
await pad.remove();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ exports.deleteGroup = async (groupID) => {
|
||||||
// record because deleting a session updates the group2sessions record.
|
// record because deleting a session updates the group2sessions record.
|
||||||
const {sessionIDs = {}} = await db.get(`group2sessions:${groupID}`) || {};
|
const {sessionIDs = {}} = await db.get(`group2sessions:${groupID}`) || {};
|
||||||
await Promise.all(Object.keys(sessionIDs).map(async (sessionId) => {
|
await Promise.all(Object.keys(sessionIDs).map(async (sessionId) => {
|
||||||
await sessionManager.deleteSession(sessionId);
|
await deleteSession(sessionId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -68,14 +68,14 @@ exports.deleteGroup = async (groupID) => {
|
||||||
await db.remove(`group:${groupID}`);
|
await db.remove(`group:${groupID}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.doesGroupExist = async (groupID) => {
|
export const doesGroupExist = async (groupID) => {
|
||||||
// try to get the group entry
|
// try to get the group entry
|
||||||
const group = await db.get(`group:${groupID}`);
|
const group = await db.get(`group:${groupID}`);
|
||||||
|
|
||||||
return (group != null);
|
return (group != null);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createGroup = async () => {
|
export const createGroup = async () => {
|
||||||
const groupID = `g.${randomString(16)}`;
|
const groupID = `g.${randomString(16)}`;
|
||||||
await db.set(`group:${groupID}`, {pads: {}, mappings: {}});
|
await db.set(`group:${groupID}`, {pads: {}, mappings: {}});
|
||||||
// Add the group to the `groups` record after the group's individual record is created so that
|
// Add the group to the `groups` record after the group's individual record is created so that
|
||||||
|
@ -85,7 +85,7 @@ exports.createGroup = async () => {
|
||||||
return {groupID};
|
return {groupID};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createGroupIfNotExistsFor = async (groupMapper) => {
|
export const createGroupIfNotExistsFor = async (groupMapper) => {
|
||||||
if (typeof groupMapper !== 'string') {
|
if (typeof groupMapper !== 'string') {
|
||||||
throw new CustomError('groupMapper is not a string', 'apierror');
|
throw new CustomError('groupMapper is not a string', 'apierror');
|
||||||
}
|
}
|
||||||
|
@ -103,19 +103,19 @@ exports.createGroupIfNotExistsFor = async (groupMapper) => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
|
export const createGroupPad = async (groupID, padName, text, authorId = '') => {
|
||||||
// create the padID
|
// create the padID
|
||||||
const padID = `${groupID}$${padName}`;
|
const padID = `${groupID}$${padName}`;
|
||||||
|
|
||||||
// ensure group exists
|
// ensure group exists
|
||||||
const groupExists = await exports.doesGroupExist(groupID);
|
const groupExists = await doesGroupExist(groupID);
|
||||||
|
|
||||||
if (!groupExists) {
|
if (!groupExists) {
|
||||||
throw new CustomError('groupID does not exist', 'apierror');
|
throw new CustomError('groupID does not exist', 'apierror');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure pad doesn't exist already
|
// ensure pad doesn't exist already
|
||||||
const padExists = await padManager.doesPadExists(padID);
|
const padExists = await doesPadExist(padID);
|
||||||
|
|
||||||
if (padExists) {
|
if (padExists) {
|
||||||
// pad exists already
|
// pad exists already
|
||||||
|
@ -123,7 +123,7 @@ exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the pad
|
// create the pad
|
||||||
await padManager.getPad(padID, text, authorId);
|
await getPad(padID, text, authorId);
|
||||||
|
|
||||||
// create an entry in the group for this pad
|
// create an entry in the group for this pad
|
||||||
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
|
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
|
||||||
|
@ -131,7 +131,7 @@ exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
|
||||||
return {padID};
|
return {padID};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.listPads = async (groupID) => {
|
export const listPads = async (groupID) => {
|
||||||
const exists = await exports.doesGroupExist(groupID);
|
const exists = await exports.doesGroupExist(groupID);
|
||||||
|
|
||||||
// ensure the group exists
|
// ensure the group exists
|
|
@ -3,15 +3,16 @@
|
||||||
* The pad object, defined with joose
|
* The pad object, defined with joose
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const AttributeMap = require('../../static/js/AttributeMap');
|
import AttributeMap from '../../static/js/AttributeMap';
|
||||||
const Changeset = require('../../static/js/Changeset');
|
import Changeset from '../../static/js/Changeset';
|
||||||
const ChatMessage = require('../../static/js/ChatMessage');
|
import ChatMessage from '../../static/js/ChatMessage';
|
||||||
const AttributePool = require('../../static/js/AttributePool');
|
import {AttributePool} from '../../static/js/AttributePool';
|
||||||
const Stream = require('../utils/Stream');
|
import Stream from '../utils/Stream';
|
||||||
const assert = require('assert').strict;
|
import assert, {strict} from 'assert'
|
||||||
const db = require('./DB');
|
import {db} from './DB';
|
||||||
const settings = require('../utils/Settings');
|
import {defaultPadText} from '../utils/Settings';
|
||||||
const authorManager = require('./AuthorManager');
|
import {addPad, getAuthorColorId, getAuthorName, getColorPalette, removePad} from './AuthorManager';
|
||||||
|
import {Revision} from "../models/Revision";
|
||||||
const padManager = require('./PadManager');
|
const padManager = require('./PadManager');
|
||||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||||
const groupManager = require('./GroupManager');
|
const groupManager = require('./GroupManager');
|
||||||
|
@ -32,15 +33,24 @@ exports.cleanText = (txt) => txt.replace(/\r\n/g, '\n')
|
||||||
.replace(/\t/g, ' ')
|
.replace(/\t/g, ' ')
|
||||||
.replace(/\xa0/g, ' ');
|
.replace(/\xa0/g, ' ');
|
||||||
|
|
||||||
class Pad {
|
export class Pad {
|
||||||
|
private db: any;
|
||||||
|
private atext: any;
|
||||||
|
private pool: AttributePool;
|
||||||
|
private head: number;
|
||||||
|
private chatHead: number;
|
||||||
|
private publicStatus: boolean;
|
||||||
|
private id: string;
|
||||||
|
private savedRevisions: Revision[];
|
||||||
/**
|
/**
|
||||||
|
* @param id the id of this pad
|
||||||
* @param [database] - Database object to access this pad's records (and only this pad's records;
|
* @param [database] - Database object to access this pad's records (and only this pad's records;
|
||||||
* the shared global Etherpad database object is still used for all other pad accesses, such
|
* the shared global Etherpad database object is still used for all other pad accesses, such
|
||||||
* as copying the pad). Defaults to the shared global Etherpad database object. This parameter
|
* as copying the pad). Defaults to the shared global Etherpad database object. This parameter
|
||||||
* can be used to shard pad storage across multiple database backends, to put each pad in its
|
* can be used to shard pad storage across multiple database backends, to put each pad in its
|
||||||
* own database table, or to validate imported pad data before it is written to the database.
|
* own database table, or to validate imported pad data before it is written to the database.
|
||||||
*/
|
*/
|
||||||
constructor(id, database = db) {
|
constructor(id: string, database = db) {
|
||||||
this.db = database;
|
this.db = database;
|
||||||
this.atext = Changeset.makeAText('\n');
|
this.atext = Changeset.makeAText('\n');
|
||||||
this.pool = new AttributePool();
|
this.pool = new AttributePool();
|
||||||
|
@ -99,7 +109,7 @@ class Pad {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.saveToDatabase(),
|
this.saveToDatabase(),
|
||||||
authorId && authorManager.addPad(authorId, this.id),
|
authorId && addPad(authorId, this.id),
|
||||||
hooks.aCallAll(hook, {
|
hooks.aCallAll(hook, {
|
||||||
pad: this,
|
pad: this,
|
||||||
authorId,
|
authorId,
|
||||||
|
@ -121,7 +131,7 @@ class Pad {
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
const o = {...this, pool: this.pool.toJsonable()};
|
const o:{db: any, id: any} = {...this, pool: this.pool.toJsonable()}
|
||||||
delete o.db;
|
delete o.db;
|
||||||
delete o.id;
|
delete o.id;
|
||||||
return o;
|
return o;
|
||||||
|
@ -190,10 +200,10 @@ class Pad {
|
||||||
async getAllAuthorColors() {
|
async getAllAuthorColors() {
|
||||||
const authorIds = this.getAllAuthors();
|
const authorIds = this.getAllAuthors();
|
||||||
const returnTable = {};
|
const returnTable = {};
|
||||||
const colorPalette = authorManager.getColorPalette();
|
const colorPalette = getColorPalette();
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId) => {
|
authorIds.map((authorId) => getAuthorColorId(authorId).then((colorId) => {
|
||||||
// colorId might be a hex color or an number out of the palette
|
// colorId might be a hex color or an number out of the palette
|
||||||
returnTable[authorId] = colorPalette[colorId] || colorId;
|
returnTable[authorId] = colorPalette[colorId] || colorId;
|
||||||
})));
|
})));
|
||||||
|
@ -315,7 +325,7 @@ class Pad {
|
||||||
const entry = await this.db.get(`pad:${this.id}:chat:${entryNum}`);
|
const entry = await this.db.get(`pad:${this.id}:chat:${entryNum}`);
|
||||||
if (entry == null) return null;
|
if (entry == null) return null;
|
||||||
const message = ChatMessage.fromObject(entry);
|
const message = ChatMessage.fromObject(entry);
|
||||||
message.displayName = await authorManager.getAuthorName(message.authorId);
|
message.displayName = await getAuthorName(message.authorId);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +362,7 @@ class Pad {
|
||||||
if ('pool' in value) this.pool = new AttributePool().fromJsonable(value.pool);
|
if ('pool' in value) this.pool = new AttributePool().fromJsonable(value.pool);
|
||||||
} else {
|
} else {
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
const context = {pad: this, authorId, type: 'text', content: settings.defaultPadText};
|
const context = {pad: this, authorId, type: 'text', content: defaultPadText};
|
||||||
await hooks.aCallAll('padDefaultContent', context);
|
await hooks.aCallAll('padDefaultContent', context);
|
||||||
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
|
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
|
||||||
text = exports.cleanText(context.content);
|
text = exports.cleanText(context.content);
|
||||||
|
@ -454,7 +464,7 @@ class Pad {
|
||||||
async copyAuthorInfoToDestinationPad(destinationID) {
|
async copyAuthorInfoToDestinationPad(destinationID) {
|
||||||
// add the new sourcePad to all authors who contributed to the old one
|
// add the new sourcePad to all authors who contributed to the old one
|
||||||
await Promise.all(this.getAllAuthors().map(
|
await Promise.all(this.getAllAuthors().map(
|
||||||
(authorID) => authorManager.addPad(authorID, destinationID)));
|
(authorID) => addPad(authorID, destinationID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyPadWithoutHistory(destinationID, force, authorId = '') {
|
async copyPadWithoutHistory(destinationID, force, authorId = '') {
|
||||||
|
@ -557,7 +567,7 @@ class Pad {
|
||||||
|
|
||||||
// remove pad from all authors who contributed
|
// remove pad from all authors who contributed
|
||||||
this.getAllAuthors().forEach((authorId) => {
|
this.getAllAuthors().forEach((authorId) => {
|
||||||
p.push(authorManager.removePad(authorId, padID));
|
p.push(removePad(authorId, padID));
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete the pad entry and delete pad from padManager
|
// delete the pad entry and delete pad from padManager
|
||||||
|
@ -587,12 +597,13 @@ class Pad {
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the saved revision object
|
// build the saved revision object
|
||||||
const savedRevision = {};
|
const savedRevision:Revision = {
|
||||||
savedRevision.revNum = revNum;
|
label: label || `Revision ${revNum}`,
|
||||||
savedRevision.savedById = savedById;
|
revNum: revNum,
|
||||||
savedRevision.label = label || `Revision ${revNum}`;
|
savedById: savedById,
|
||||||
savedRevision.timestamp = Date.now();
|
timestamp: Date.now(),
|
||||||
savedRevision.id = randomString(10);
|
id: randomString(10)
|
||||||
|
}
|
||||||
|
|
||||||
// save this new saved revision
|
// save this new saved revision
|
||||||
this.savedRevisions.push(savedRevision);
|
this.savedRevisions.push(savedRevision);
|
|
@ -19,9 +19,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CustomError = require('../utils/customError');
|
import CustomError from '../utils/customError';
|
||||||
const Pad = require('../db/Pad');
|
import {Pad} from './Pad';
|
||||||
const db = require('./DB');
|
import {db} from './DB';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache of all loaded Pads.
|
* A cache of all loaded Pads.
|
||||||
|
@ -50,6 +50,9 @@ const globalPads = {
|
||||||
* Updated without db access as new pads are created/old ones removed.
|
* Updated without db access as new pads are created/old ones removed.
|
||||||
*/
|
*/
|
||||||
const padList = new class {
|
const padList = new class {
|
||||||
|
private _cachedList: any[];
|
||||||
|
private _list: Set<any>;
|
||||||
|
private _loaded: Promise<void>
|
||||||
constructor() {
|
constructor() {
|
||||||
this._cachedList = null;
|
this._cachedList = null;
|
||||||
this._list = new Set();
|
this._list = new Set();
|
||||||
|
@ -94,9 +97,9 @@ const padList = new class {
|
||||||
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
|
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
|
||||||
* applicable).
|
* applicable).
|
||||||
*/
|
*/
|
||||||
exports.getPad = async (id, text, authorId = '') => {
|
export const getPad = async (id, text?, authorId = '') => {
|
||||||
// check if this is a valid padId
|
// check if this is a valid padId
|
||||||
if (!exports.isValidPadId(id)) {
|
if (!isValidPadId(id)) {
|
||||||
throw new CustomError(`${id} is not a valid padId`, 'apierror');
|
throw new CustomError(`${id} is not a valid padId`, 'apierror');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +124,7 @@ exports.getPad = async (id, text, authorId = '') => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to load pad
|
// try to load pad
|
||||||
pad = new Pad.Pad(id);
|
pad = new Pad(id);
|
||||||
|
|
||||||
// initialize the pad
|
// initialize the pad
|
||||||
await pad.init(text, authorId);
|
await pad.init(text, authorId);
|
||||||
|
@ -131,21 +134,18 @@ exports.getPad = async (id, text, authorId = '') => {
|
||||||
return pad;
|
return pad;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.listAllPads = async () => {
|
export const listAllPads = async () => {
|
||||||
const padIDs = await padList.getPads();
|
const padIDs = await padList.getPads();
|
||||||
|
|
||||||
return {padIDs};
|
return {padIDs};
|
||||||
};
|
};
|
||||||
|
|
||||||
// checks if a pad exists
|
// checks if a pad exists
|
||||||
exports.doesPadExist = async (padId) => {
|
export const doesPadExist = async (padId) => {
|
||||||
const value = await db.get(`pad:${padId}`);
|
const value = await db.get(`pad:${padId}`);
|
||||||
|
|
||||||
return (value != null && value.atext);
|
return (value != null && value.atext);
|
||||||
};
|
}
|
||||||
|
|
||||||
// alias for backwards compatibility
|
|
||||||
exports.doesPadExists = exports.doesPadExist;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of padId transformations. These represent changes in pad name policy over
|
* An array of padId transformations. These represent changes in pad name policy over
|
||||||
|
@ -157,9 +157,9 @@ const padIdTransforms = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// returns a sanitized padId, respecting legacy pad id formats
|
// returns a sanitized padId, respecting legacy pad id formats
|
||||||
exports.sanitizePadId = async (padId) => {
|
export const sanitizePadId = async (padId) => {
|
||||||
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
|
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
|
||||||
const exists = await exports.doesPadExist(padId);
|
const exists = await doesPadExist(padId);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return padId;
|
return padId;
|
||||||
|
@ -174,19 +174,19 @@ exports.sanitizePadId = async (padId) => {
|
||||||
return padId;
|
return padId;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
export const isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the pad from database and unloads it.
|
* Removes the pad from database and unloads it.
|
||||||
*/
|
*/
|
||||||
exports.removePad = async (padId) => {
|
export const removePad = async (padId) => {
|
||||||
const p = db.remove(`pad:${padId}`);
|
const p = db.remove(`pad:${padId}`);
|
||||||
exports.unloadPad(padId);
|
unloadPad(padId);
|
||||||
padList.removePad(padId);
|
padList.removePad(padId);
|
||||||
await p;
|
await p;
|
||||||
};
|
};
|
||||||
|
|
||||||
// removes a pad from the cache
|
// removes a pad from the cache
|
||||||
exports.unloadPad = (padId) => {
|
export const unloadPad = (padId) => {
|
||||||
globalPads.remove(padId);
|
globalPads.remove(padId);
|
||||||
};
|
};
|
|
@ -20,21 +20,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
const db = require('./DB');
|
import {db} from './DB';
|
||||||
const randomString = require('../utils/randomstring');
|
import randomString from '../utils/randomstring';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks if the id pattern matches a read-only pad id
|
* checks if the id pattern matches a read-only pad id
|
||||||
* @param {String} the pad's id
|
* @param {String} id the pad's id
|
||||||
*/
|
*/
|
||||||
exports.isReadOnlyId = (id) => id.startsWith('r.');
|
export const isReadOnlyId = (id: string) => id.startsWith('r.');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a read only id for a pad
|
* returns a read only id for a pad
|
||||||
* @param {String} padId the id of the pad
|
* @param {String} padId the id of the pad
|
||||||
*/
|
*/
|
||||||
exports.getReadOnlyId = async (padId) => {
|
export const getReadOnlyId = async (padId) => {
|
||||||
// check if there is a pad2readonly entry
|
// check if there is a pad2readonly entry
|
||||||
let readOnlyId = await db.get(`pad2readonly:${padId}`);
|
let readOnlyId = await db.get(`pad2readonly:${padId}`);
|
||||||
|
|
||||||
|
@ -54,13 +54,13 @@ exports.getReadOnlyId = async (padId) => {
|
||||||
* returns the padId for a read only id
|
* returns the padId for a read only id
|
||||||
* @param {String} readOnlyId read only id
|
* @param {String} readOnlyId read only id
|
||||||
*/
|
*/
|
||||||
exports.getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readOnlyId}`);
|
export const getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readOnlyId}`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the padId and readonlyPadId in an object for any id
|
* returns the padId and readonlyPadId in an object for any id
|
||||||
* @param {String} padIdOrReadonlyPadId read only id or real pad id
|
* @param {String} id padIdOrReadonlyPadId read only id or real pad id
|
||||||
*/
|
*/
|
||||||
exports.getIds = async (id) => {
|
export const getIds = async (id: string) => {
|
||||||
const readonly = exports.isReadOnlyId(id);
|
const readonly = exports.isReadOnlyId(id);
|
||||||
|
|
||||||
// Might be null, if this is an unknown read-only id
|
// Might be null, if this is an unknown read-only id
|
|
@ -19,18 +19,29 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const authorManager = require('./AuthorManager');
|
import {getAuthorId} from "./AuthorManager";
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
|
||||||
const padManager = require('./PadManager');
|
|
||||||
const readOnlyManager = require('./ReadOnlyManager');
|
|
||||||
const sessionManager = require('./SessionManager');
|
|
||||||
const settings = require('../utils/Settings');
|
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
|
||||||
const log4js = require('log4js');
|
|
||||||
const authLogger = log4js.getLogger('auth');
|
|
||||||
const {padutils} = require('../../static/js/pad_utils');
|
|
||||||
|
|
||||||
const DENY = Object.freeze({accessStatus: 'deny'});
|
import hooks from "../../static/js/pluginfw/hooks.js";
|
||||||
|
|
||||||
|
import {doesPadExist, getPad} from "./PadManager";
|
||||||
|
|
||||||
|
import {getPadId} from "./ReadOnlyManager";
|
||||||
|
|
||||||
|
import {findAuthorID} from "./SessionManager";
|
||||||
|
|
||||||
|
import {editOnly, loadTest, requireAuthentication, requireSession} from "../utils/Settings";
|
||||||
|
|
||||||
|
import webaccess from "../hooks/express/webaccess";
|
||||||
|
|
||||||
|
import log4js from "log4js";
|
||||||
|
|
||||||
|
import {padutils} from "../../static/js/pad_utils";
|
||||||
|
import {isReadOnlyId} from "./ReadOnlyManager.js";
|
||||||
|
|
||||||
|
const authLogger = log4js.getLogger('auth');
|
||||||
|
|
||||||
|
|
||||||
|
const DENY = Object.freeze({accessStatus: 'deny', authorID: null});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the user can access a pad.
|
* Determines whether the user can access a pad.
|
||||||
|
@ -50,17 +61,17 @@ const DENY = Object.freeze({accessStatus: 'deny'});
|
||||||
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
|
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
|
||||||
* each other (which might allow them to gain privileges).
|
* each other (which might allow them to gain privileges).
|
||||||
*/
|
*/
|
||||||
exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
export const checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
||||||
if (!padID) {
|
if (!padID) {
|
||||||
authLogger.debug('access denied: missing padID');
|
authLogger.debug('access denied: missing padID');
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canCreate = !settings.editOnly;
|
let canCreate = !editOnly;
|
||||||
|
|
||||||
if (readOnlyManager.isReadOnlyId(padID)) {
|
if (isReadOnlyId(padID)) {
|
||||||
canCreate = false;
|
canCreate = false;
|
||||||
padID = await readOnlyManager.getPadId(padID);
|
padID = await getPadId(padID);
|
||||||
if (padID == null) {
|
if (padID == null) {
|
||||||
authLogger.debug('access denied: read-only pad ID for a pad that does not exist');
|
authLogger.debug('access denied: read-only pad ID for a pad that does not exist');
|
||||||
return DENY;
|
return DENY;
|
||||||
|
@ -68,10 +79,10 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authentication and authorization checks.
|
// Authentication and authorization checks.
|
||||||
if (settings.loadTest) {
|
if (loadTest) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'bypassing socket.io authentication and authorization checks due to settings.loadTest');
|
'bypassing socket.io authentication and authorization checks due to settings.loadTest');
|
||||||
} else if (settings.requireAuthentication) {
|
} else if (requireAuthentication) {
|
||||||
if (userSettings == null) {
|
if (userSettings == null) {
|
||||||
authLogger.debug('access denied: authentication is required');
|
authLogger.debug('access denied: authentication is required');
|
||||||
return DENY;
|
return DENY;
|
||||||
|
@ -96,14 +107,14 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const padExists = await padManager.doesPadExist(padID);
|
const padExists = await doesPadExist(padID);
|
||||||
if (!padExists && !canCreate) {
|
if (!padExists && !canCreate) {
|
||||||
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
|
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionAuthorID = await sessionManager.findAuthorID(padID.split('$')[0], sessionCookie);
|
const sessionAuthorID = await findAuthorID(padID.split('$')[0], sessionCookie);
|
||||||
if (settings.requireSession && !sessionAuthorID) {
|
if (requireSession && !sessionAuthorID) {
|
||||||
authLogger.debug('access denied: HTTP API session is required');
|
authLogger.debug('access denied: HTTP API session is required');
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +126,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
||||||
|
|
||||||
const grant = {
|
const grant = {
|
||||||
accessStatus: 'grant',
|
accessStatus: 'grant',
|
||||||
authorID: sessionAuthorID || await authorManager.getAuthorId(token, userSettings),
|
authorID: sessionAuthorID || await getAuthorId(token, userSettings),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!padID.includes('$')) {
|
if (!padID.includes('$')) {
|
||||||
|
@ -132,7 +143,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
|
||||||
return grant;
|
return grant;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pad = await padManager.getPad(padID);
|
const pad = await getPad(padID);
|
||||||
|
|
||||||
if (!pad.getPublicStatus() && sessionAuthorID == null) {
|
if (!pad.getPublicStatus() && sessionAuthorID == null) {
|
||||||
authLogger.debug('access denied: must have an HTTP API session to access private group pads');
|
authLogger.debug('access denied: must have an HTTP API session to access private group pads');
|
|
@ -36,7 +36,7 @@ const authorManager = require('./AuthorManager');
|
||||||
* sessionCookie, and is bound to a group with the given ID, then this returns the author ID
|
* sessionCookie, and is bound to a group with the given ID, then this returns the author ID
|
||||||
* bound to the session. Otherwise, returns undefined.
|
* bound to the session. Otherwise, returns undefined.
|
||||||
*/
|
*/
|
||||||
exports.findAuthorID = async (groupID, sessionCookie) => {
|
export const findAuthorID = async (groupID, sessionCookie) => {
|
||||||
if (!sessionCookie) return undefined;
|
if (!sessionCookie) return undefined;
|
||||||
/*
|
/*
|
||||||
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
|
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
|
||||||
|
@ -64,7 +64,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
|
||||||
const sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
|
const sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
|
||||||
const sessionInfoPromises = sessionIDs.map(async (id) => {
|
const sessionInfoPromises = sessionIDs.map(async (id) => {
|
||||||
try {
|
try {
|
||||||
return await exports.getSessionInfo(id);
|
return await getSessionInfo(id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.message === 'sessionID does not exist') {
|
if (err.message === 'sessionID does not exist') {
|
||||||
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
|
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
|
||||||
|
@ -81,7 +81,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
|
||||||
return sessionInfo.authorID;
|
return sessionInfo.authorID;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.doesSessionExist = async (sessionID) => {
|
export const doesSessionExist = async (sessionID) => {
|
||||||
// check if the database entry of this session exists
|
// check if the database entry of this session exists
|
||||||
const session = await db.get(`session:${sessionID}`);
|
const session = await db.get(`session:${sessionID}`);
|
||||||
return (session != null);
|
return (session != null);
|
||||||
|
@ -90,7 +90,7 @@ exports.doesSessionExist = async (sessionID) => {
|
||||||
/**
|
/**
|
||||||
* Creates a new session between an author and a group
|
* Creates a new session between an author and a group
|
||||||
*/
|
*/
|
||||||
exports.createSession = async (groupID, authorID, validUntil) => {
|
export const createSession = async (groupID, authorID, validUntil) => {
|
||||||
// check if the group exists
|
// check if the group exists
|
||||||
const groupExists = await groupManager.doesGroupExist(groupID);
|
const groupExists = await groupManager.doesGroupExist(groupID);
|
||||||
if (!groupExists) {
|
if (!groupExists) {
|
||||||
|
@ -146,7 +146,7 @@ exports.createSession = async (groupID, authorID, validUntil) => {
|
||||||
return {sessionID};
|
return {sessionID};
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getSessionInfo = async (sessionID) => {
|
export const getSessionInfo = async (sessionID) => {
|
||||||
// check if the database entry of this session exists
|
// check if the database entry of this session exists
|
||||||
const session = await db.get(`session:${sessionID}`);
|
const session = await db.get(`session:${sessionID}`);
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ exports.getSessionInfo = async (sessionID) => {
|
||||||
/**
|
/**
|
||||||
* Deletes a session
|
* Deletes a session
|
||||||
*/
|
*/
|
||||||
exports.deleteSession = async (sessionID) => {
|
export const deleteSession = async (sessionID) => {
|
||||||
// ensure that the session exists
|
// ensure that the session exists
|
||||||
const session = await db.get(`session:${sessionID}`);
|
const session = await db.get(`session:${sessionID}`);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
@ -186,7 +186,7 @@ exports.deleteSession = async (sessionID) => {
|
||||||
await db.remove(`session:${sessionID}`);
|
await db.remove(`session:${sessionID}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.listSessionsOfGroup = async (groupID) => {
|
export const listSessionsOfGroup = async (groupID) => {
|
||||||
// check that the group exists
|
// check that the group exists
|
||||||
const exists = await groupManager.doesGroupExist(groupID);
|
const exists = await groupManager.doesGroupExist(groupID);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
@ -197,7 +197,7 @@ exports.listSessionsOfGroup = async (groupID) => {
|
||||||
return sessions;
|
return sessions;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.listSessionsOfAuthor = async (authorID) => {
|
export const listSessionsOfAuthor = async (authorID) => {
|
||||||
// check that the author exists
|
// check that the author exists
|
||||||
const exists = await authorManager.doesAuthorExist(authorID);
|
const exists = await authorManager.doesAuthorExist(authorID);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
@ -218,8 +218,7 @@ const listSessionsWithDBKey = async (dbkey) => {
|
||||||
// iterate through the sessions and get the sessioninfos
|
// iterate through the sessions and get the sessioninfos
|
||||||
for (const sessionID of Object.keys(sessions || {})) {
|
for (const sessionID of Object.keys(sessions || {})) {
|
||||||
try {
|
try {
|
||||||
const sessionInfo = await exports.getSessionInfo(sessionID);
|
sessions[sessionID] = await getSessionInfo(sessionID);
|
||||||
sessions[sessionID] = sessionInfo;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'apierror') {
|
if (err.name === 'apierror') {
|
||||||
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
|
@ -1,13 +1,19 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const DB = require('./DB');
|
import {db} from "./DB";
|
||||||
const Store = require('express-session').Store;
|
|
||||||
const log4js = require('log4js');
|
import {Store} from "express-session";
|
||||||
const util = require('util');
|
|
||||||
|
import log4js from "log4js";
|
||||||
|
|
||||||
|
import util from "util";
|
||||||
|
import {SessionModel} from "../models/SessionModel";
|
||||||
|
|
||||||
const logger = log4js.getLogger('SessionStore');
|
const logger = log4js.getLogger('SessionStore');
|
||||||
|
|
||||||
class SessionStore extends Store {
|
class SessionStore extends Store {
|
||||||
|
private _refresh: any;
|
||||||
|
private _expirations: Map<any, any>;
|
||||||
/**
|
/**
|
||||||
* @param {?number} [refresh] - How often (in milliseconds) `touch()` will update a session's
|
* @param {?number} [refresh] - How often (in milliseconds) `touch()` will update a session's
|
||||||
* database record with the cookie's latest expiration time. If the difference between the
|
* database record with the cookie's latest expiration time. If the difference between the
|
||||||
|
@ -34,10 +40,10 @@ class SessionStore extends Store {
|
||||||
for (const {timeout} of this._expirations.values()) clearTimeout(timeout);
|
for (const {timeout} of this._expirations.values()) clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateExpirations(sid, sess, updateDbExp = true) {
|
async _updateExpirations(sid, sess: SessionModel, updateDbExp = true) {
|
||||||
const exp = this._expirations.get(sid) || {};
|
const exp = this._expirations.get(sid) || {};
|
||||||
clearTimeout(exp.timeout);
|
clearTimeout(exp.timeout);
|
||||||
const {cookie: {expires} = {}} = sess || {};
|
const {cookie: {expires} = {expires: sess.cookie.expires}} = sess || {cookie:{expires:undefined}};
|
||||||
if (expires) {
|
if (expires) {
|
||||||
const sessExp = new Date(expires).getTime();
|
const sessExp = new Date(expires).getTime();
|
||||||
if (updateDbExp) exp.db = sessExp;
|
if (updateDbExp) exp.db = sessExp;
|
||||||
|
@ -64,12 +70,12 @@ class SessionStore extends Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _write(sid, sess) {
|
async _write(sid, sess) {
|
||||||
await DB.set(`sessionstorage:${sid}`, sess);
|
await db.set(`sessionstorage:${sid}`, sess);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _get(sid) {
|
async _get(sid) {
|
||||||
logger.debug(`GET ${sid}`);
|
logger.debug(`GET ${sid}`);
|
||||||
const s = await DB.get(`sessionstorage:${sid}`);
|
const s = await db.get(`sessionstorage:${sid}`);
|
||||||
return await this._updateExpirations(sid, s);
|
return await this._updateExpirations(sid, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +89,7 @@ class SessionStore extends Store {
|
||||||
logger.debug(`DESTROY ${sid}`);
|
logger.debug(`DESTROY ${sid}`);
|
||||||
clearTimeout((this._expirations.get(sid) || {}).timeout);
|
clearTimeout((this._expirations.get(sid) || {}).timeout);
|
||||||
this._expirations.delete(sid);
|
this._expirations.delete(sid);
|
||||||
await DB.remove(`sessionstorage:${sid}`);
|
await db.remove(`sessionstorage:${sid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: express-session might call touch() before it calls set() for the first time. Ideally this
|
// Note: express-session might call touch() before it calls set() for the first time. Ideally this
|
||||||
|
@ -110,4 +116,4 @@ for (const m of ['get', 'set', 'destroy', 'touch']) {
|
||||||
SessionStore.prototype[m] = util.callbackify(SessionStore.prototype[`_${m}`]);
|
SessionStore.prototype[m] = util.callbackify(SessionStore.prototype[`_${m}`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SessionStore;
|
export default SessionStore;
|
|
@ -20,56 +20,61 @@
|
||||||
* require("./index").require("./path/to/template.ejs")
|
* require("./index").require("./path/to/template.ejs")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ejs = require('ejs');
|
import ejs from 'ejs';
|
||||||
const fs = require('fs');
|
import fs from "fs";
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
|
||||||
const path = require('path');
|
import hooks from "../../static/js/pluginfw/hooks.js";
|
||||||
const resolve = require('resolve');
|
|
||||||
const settings = require('../utils/Settings');
|
import path from "path";
|
||||||
|
|
||||||
|
import resolve from "resolve";
|
||||||
|
|
||||||
|
import {maxAge} from "../utils/Settings";
|
||||||
|
|
||||||
const templateCache = new Map();
|
const templateCache = new Map();
|
||||||
|
|
||||||
exports.info = {
|
export const info = {
|
||||||
__output_stack: [],
|
__output_stack: [],
|
||||||
block_stack: [],
|
block_stack: [],
|
||||||
file_stack: [],
|
file_stack: [],
|
||||||
args: [],
|
args: [], __output: undefined
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCurrentFile = () => exports.info.file_stack[exports.info.file_stack.length - 1];
|
const getCurrentFile = () => info.file_stack[info.file_stack.length - 1];
|
||||||
|
|
||||||
exports._init = (b, recursive) => {
|
export const _init = (b, recursive) => {
|
||||||
exports.info.__output_stack.push(exports.info.__output);
|
info.__output_stack.push(info.__output)
|
||||||
exports.info.__output = b;
|
info.__output = b
|
||||||
};
|
};
|
||||||
|
|
||||||
exports._exit = (b, recursive) => {
|
export const _exit = (b, recursive) => {
|
||||||
exports.info.__output = exports.info.__output_stack.pop();
|
info.__output = info.__output_stack.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.begin_block = (name) => {
|
export const begin_block = (name) => {
|
||||||
exports.info.block_stack.push(name);
|
info.block_stack.push(name);
|
||||||
exports.info.__output_stack.push(exports.info.__output.get());
|
info.__output_stack.push(info.__output.get());
|
||||||
exports.info.__output.set('');
|
info.__output.set('');
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.end_block = () => {
|
export const end_block = () => {
|
||||||
const name = exports.info.block_stack.pop();
|
const name = info.block_stack.pop();
|
||||||
const renderContext = exports.info.args[exports.info.args.length - 1];
|
const renderContext = info.args[info.args.length - 1];
|
||||||
const content = exports.info.__output.get();
|
const content = info.__output.get();
|
||||||
exports.info.__output.set(exports.info.__output_stack.pop());
|
info.__output.set(info.__output_stack.pop());
|
||||||
const args = {content, renderContext};
|
const args = {content, renderContext};
|
||||||
hooks.callAll(`eejsBlock_${name}`, args);
|
hooks.callAll(`eejsBlock_${name}`, args);
|
||||||
exports.info.__output.set(exports.info.__output.get().concat(args.content));
|
info.__output.set(info.__output.get().concat(args.content));
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.require = (name, args, mod) => {
|
export const required = (name, args?, mod?) => {
|
||||||
if (args == null) args = {};
|
if (args == null) args = {};
|
||||||
|
|
||||||
let basedir = __dirname;
|
let basedir = __dirname;
|
||||||
let paths = [];
|
let paths = [];
|
||||||
|
|
||||||
if (exports.info.file_stack.length) {
|
if (info.file_stack.length) {
|
||||||
basedir = path.dirname(getCurrentFile().path);
|
basedir = path.dirname(getCurrentFile().path);
|
||||||
}
|
}
|
||||||
if (mod) {
|
if (mod) {
|
||||||
|
@ -82,18 +87,18 @@ exports.require = (name, args, mod) => {
|
||||||
args.e = exports;
|
args.e = exports;
|
||||||
args.require = require;
|
args.require = require;
|
||||||
|
|
||||||
const cache = settings.maxAge !== 0;
|
const cache = maxAge !== 0;
|
||||||
const template = cache && templateCache.get(ejspath) || ejs.compile(
|
const template = cache && templateCache.get(ejspath) || ejs.compile(
|
||||||
'<% e._init({get: () => __output, set: (s) => { __output = s; }}); %>' +
|
'<% e._init({get: () => __output, set: (s) => { __output = s; }}); %>' +
|
||||||
`${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`,
|
`${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`,
|
||||||
{filename: ejspath});
|
{filename: ejspath});
|
||||||
if (cache) templateCache.set(ejspath, template);
|
if (cache) templateCache.set(ejspath, template);
|
||||||
|
|
||||||
exports.info.args.push(args);
|
info.args.push(args);
|
||||||
exports.info.file_stack.push({path: ejspath});
|
info.file_stack.push({path: ejspath});
|
||||||
const res = template(args);
|
const res = template(args);
|
||||||
exports.info.file_stack.pop();
|
info.file_stack.pop();
|
||||||
exports.info.args.pop();
|
info.args.pop();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
|
@ -19,12 +19,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const absolutePaths = require('../utils/AbsolutePaths');
|
import absolutePaths from '../utils/AbsolutePaths';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const api = require('../db/API');
|
import * as api from '../db/API';
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const padManager = require('../db/PadManager');
|
import {sanitizePadId} from '../db/PadManager';
|
||||||
const randomString = require('../utils/randomstring');
|
import randomString from '../utils/randomstring';
|
||||||
const argv = require('../utils/Cli').argv;
|
const argv = require('../utils/Cli').argv;
|
||||||
const createHTTPError = require('http-errors');
|
const createHTTPError = require('http-errors');
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of all functions
|
// a list of all functions
|
||||||
const version = {};
|
export const version = {};
|
||||||
|
|
||||||
version['1'] = {
|
version['1'] = {
|
||||||
createGroup: [],
|
createGroup: [],
|
||||||
|
@ -158,10 +158,9 @@ version['1.3.0'] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// set the latest available API version here
|
// set the latest available API version here
|
||||||
exports.latestApiVersion = '1.3.0';
|
export const latestApiVersion = '1.3.0';
|
||||||
|
|
||||||
// exports the versions so it can be used by the new Swagger endpoint
|
// exports the versions so it can be used by the new Swagger endpoint
|
||||||
exports.version = version;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a HTTP API call
|
* Handles a HTTP API call
|
||||||
|
@ -170,7 +169,7 @@ exports.version = version;
|
||||||
* @req express request object
|
* @req express request object
|
||||||
* @res express response object
|
* @res express response object
|
||||||
*/
|
*/
|
||||||
exports.handle = async function (apiVersion, functionName, fields, req, res) {
|
export const handle = async function (apiVersion, functionName, fields, req, res) {
|
||||||
// say goodbye if this is an unknown API version
|
// say goodbye if this is an unknown API version
|
||||||
if (!(apiVersion in version)) {
|
if (!(apiVersion in version)) {
|
||||||
throw new createHTTPError.NotFound('no such api version');
|
throw new createHTTPError.NotFound('no such api version');
|
||||||
|
@ -190,13 +189,13 @@ exports.handle = async function (apiVersion, functionName, fields, req, res) {
|
||||||
|
|
||||||
// sanitize any padIDs before continuing
|
// sanitize any padIDs before continuing
|
||||||
if (fields.padID) {
|
if (fields.padID) {
|
||||||
fields.padID = await padManager.sanitizePadId(fields.padID);
|
fields.padID = await sanitizePadId(fields.padID);
|
||||||
}
|
}
|
||||||
// there was an 'else' here before - removed it to ensure
|
// there was an 'else' here before - removed it to ensure
|
||||||
// that this sanitize step can't be circumvented by forcing
|
// that this sanitize step can't be circumvented by forcing
|
||||||
// the first branch to be taken
|
// the first branch to be taken
|
||||||
if (fields.padName) {
|
if (fields.padName) {
|
||||||
fields.padName = await padManager.sanitizePadId(fields.padName);
|
fields.padName = await sanitizePadId(fields.padName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the function parameters in an array
|
// put the function parameters in an array
|
||||||
|
@ -206,6 +205,6 @@ exports.handle = async function (apiVersion, functionName, fields, req, res) {
|
||||||
return api[functionName].apply(this, functionParams);
|
return api[functionName].apply(this, functionParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.exportedForTestingOnly = {
|
export const exportedForTestingOnly = {
|
||||||
apiKey: apikey,
|
apiKey: apikey,
|
||||||
};
|
};
|
|
@ -38,7 +38,7 @@ const tempDirectory = os.tmpdir();
|
||||||
/**
|
/**
|
||||||
* do a requested export
|
* do a requested export
|
||||||
*/
|
*/
|
||||||
exports.doExport = async (req, res, padId, readOnlyId, type) => {
|
export const doExport = async (req, res, padId, readOnlyId, type) => {
|
||||||
// avoid naming the read-only file as the original pad's id
|
// avoid naming the read-only file as the original pad's id
|
||||||
let fileName = readOnlyId ? readOnlyId : padId;
|
let fileName = readOnlyId ? readOnlyId : padId;
|
||||||
|
|
||||||
|
@ -114,4 +114,4 @@ exports.doExport = async (req, res, padId, readOnlyId, type) => {
|
||||||
|
|
||||||
await fsp_unlink(destFile);
|
await fsp_unlink(destFile);
|
||||||
}
|
}
|
||||||
};
|
}
|
|
@ -21,22 +21,24 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const padManager = require('../db/PadManager');
|
import {getPad, unloadPad} from '../db/PadManager';
|
||||||
const padMessageHandler = require('./PadMessageHandler');
|
import {updatePadClients} from './PadMessageHandler';
|
||||||
const fs = require('fs').promises;
|
import path from 'path';
|
||||||
const path = require('path');
|
import {promises as fs} from "fs";
|
||||||
const settings = require('../utils/Settings');
|
|
||||||
const {Formidable} = require('formidable');
|
import {abiword, allowUnknownFileEnds, importMaxFileSize, soffice} from '../utils/Settings';
|
||||||
const os = require('os');
|
import {Formidable} from 'formidable';
|
||||||
const importHtml = require('../utils/ImportHtml');
|
import os from 'os';
|
||||||
const importEtherpad = require('../utils/ImportEtherpad');
|
import importHtml from '../utils/ImportHtml';
|
||||||
const log4js = require('log4js');
|
import importEtherpad from '../utils/ImportEtherpad';
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
import log4js from 'log4js';
|
||||||
|
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||||
|
|
||||||
const logger = log4js.getLogger('ImportHandler');
|
const logger = log4js.getLogger('ImportHandler');
|
||||||
|
|
||||||
// `status` must be a string supported by `importErrorMessage()` in `src/static/js/pad_impexp.js`.
|
// `status` must be a string supported by `importErrorMessage()` in `src/static/js/pad_impexp.js`.
|
||||||
class ImportError extends Error {
|
class ImportError extends Error {
|
||||||
|
public status: any;
|
||||||
constructor(status, ...args) {
|
constructor(status, ...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(this, ImportError);
|
if (Error.captureStackTrace) Error.captureStackTrace(this, ImportError);
|
||||||
|
@ -59,12 +61,12 @@ let converter = null;
|
||||||
let exportExtension = 'htm';
|
let exportExtension = 'htm';
|
||||||
|
|
||||||
// load abiword only if it is enabled and if soffice is disabled
|
// load abiword only if it is enabled and if soffice is disabled
|
||||||
if (settings.abiword != null && settings.soffice == null) {
|
if (abiword != null && soffice == null) {
|
||||||
converter = require('../utils/Abiword');
|
converter = require('../utils/Abiword');
|
||||||
}
|
}
|
||||||
|
|
||||||
// load soffice only if it is enabled
|
// load soffice only if it is enabled
|
||||||
if (settings.soffice != null) {
|
if (soffice != null) {
|
||||||
converter = require('../utils/LibreOffice');
|
converter = require('../utils/LibreOffice');
|
||||||
exportExtension = 'html';
|
exportExtension = 'html';
|
||||||
}
|
}
|
||||||
|
@ -86,11 +88,11 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
const form = new Formidable({
|
const form = new Formidable({
|
||||||
keepExtensions: true,
|
keepExtensions: true,
|
||||||
uploadDir: tmpDirectory,
|
uploadDir: tmpDirectory,
|
||||||
maxFileSize: settings.importMaxFileSize,
|
maxFileSize: importMaxFileSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
// locally wrapped Promise, since form.parse requires a callback
|
// locally wrapped Promise, since form.parse requires a callback
|
||||||
let srcFile = await new Promise((resolve, reject) => {
|
let srcFile = await new Promise<string>((resolve, reject) => {
|
||||||
form.parse(req, (err, fields, files) => {
|
form.parse(req, (err, fields, files) => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
logger.warn(`Import failed due to form error: ${err.stack || err}`);
|
logger.warn(`Import failed due to form error: ${err.stack || err}`);
|
||||||
|
@ -118,7 +120,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
if (fileEndingUnknown) {
|
if (fileEndingUnknown) {
|
||||||
// the file ending is not known
|
// the file ending is not known
|
||||||
|
|
||||||
if (settings.allowUnknownFileEnds === true) {
|
if (allowUnknownFileEnds === true) {
|
||||||
// we need to rename this file with a .txt ending
|
// we need to rename this file with a .txt ending
|
||||||
const oldSrcFile = srcFile;
|
const oldSrcFile = srcFile;
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
let directDatabaseAccess = false;
|
let directDatabaseAccess = false;
|
||||||
if (fileIsEtherpad) {
|
if (fileIsEtherpad) {
|
||||||
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
|
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
|
||||||
const pad = await padManager.getPad(padId, '\n', authorId);
|
const pad = await getPad(padId, '\n', authorId);
|
||||||
const headCount = pad.head;
|
const headCount = pad.head;
|
||||||
if (headCount >= 10) {
|
if (headCount >= 10) {
|
||||||
logger.warn('Aborting direct database import attempt of a pad that already has content');
|
logger.warn('Aborting direct database import attempt of a pad that already has content');
|
||||||
|
@ -186,7 +188,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
|
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
|
||||||
let pad = await padManager.getPad(padId, '\n', authorId);
|
let pad = await getPad(padId, '\n', authorId);
|
||||||
|
|
||||||
// read the text
|
// read the text
|
||||||
let text;
|
let text;
|
||||||
|
@ -215,16 +217,16 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the Pad into memory then broadcast updates to all clients
|
// Load the Pad into memory then broadcast updates to all clients
|
||||||
padManager.unloadPad(padId);
|
unloadPad(padId);
|
||||||
pad = await padManager.getPad(padId, '\n', authorId);
|
pad = await getPad(padId, '\n', authorId);
|
||||||
padManager.unloadPad(padId);
|
unloadPad(padId);
|
||||||
|
|
||||||
// Direct database access means a pad user should reload the pad and not attempt to receive
|
// Direct database access means a pad user should reload the pad and not attempt to receive
|
||||||
// updated pad data.
|
// updated pad data.
|
||||||
if (directDatabaseAccess) return true;
|
if (directDatabaseAccess) return true;
|
||||||
|
|
||||||
// tell clients to update
|
// tell clients to update
|
||||||
await padMessageHandler.updatePadClients(pad);
|
await updatePadClients(pad);
|
||||||
|
|
||||||
// clean up temporary files
|
// clean up temporary files
|
||||||
rm(srcFile);
|
rm(srcFile);
|
||||||
|
@ -233,7 +235,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.doImport = async (req, res, padId, authorId = '') => {
|
export const doImport2 = async (req, res, padId, authorId = '') => {
|
||||||
let httpStatus = 200;
|
let httpStatus = 200;
|
||||||
let code = 0;
|
let code = 0;
|
||||||
let message = 'ok';
|
let message = 'ok';
|
|
@ -19,34 +19,62 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const AttributeMap = require('../../static/js/AttributeMap');
|
import AttributeMap from '../../static/js/AttributeMap';
|
||||||
const padManager = require('../db/PadManager');
|
import {getPad} from '../db/PadManager';
|
||||||
const Changeset = require('../../static/js/Changeset');
|
import Changeset from '../../static/js/Changeset';
|
||||||
const ChatMessage = require('../../static/js/ChatMessage');
|
import ChatMessage from '../../static/js/ChatMessage';
|
||||||
const AttributePool = require('../../static/js/AttributePool');
|
import {AttributePool} from '../../static/js/AttributePool';
|
||||||
const AttributeManager = require('../../static/js/AttributeManager');
|
import AttributeManager from '../../static/js/AttributeManager';
|
||||||
const authorManager = require('../db/AuthorManager');
|
import {
|
||||||
const {padutils} = require('../../static/js/pad_utils');
|
getAuthor,
|
||||||
const readOnlyManager = require('../db/ReadOnlyManager');
|
getAuthorColorId,
|
||||||
const settings = require('../utils/Settings');
|
getAuthorName,
|
||||||
|
getColorPalette,
|
||||||
|
setAuthorColorId,
|
||||||
|
setAuthorName
|
||||||
|
} from '../db/AuthorManager';
|
||||||
|
import {padutils} from '../../static/js/pad_utils';
|
||||||
|
import {getIds} from '../db/ReadOnlyManager';
|
||||||
|
import {
|
||||||
|
abiwordAvailable,
|
||||||
|
automaticReconnectionTimeout,
|
||||||
|
commitRateLimiting,
|
||||||
|
cookie,
|
||||||
|
disableIPlogging,
|
||||||
|
exportAvailable,
|
||||||
|
indentationOnNewLine,
|
||||||
|
padOptions,
|
||||||
|
padShortcutEnabled,
|
||||||
|
randomVersionString,
|
||||||
|
scrollWhenFocusLineIsOutOfViewport,
|
||||||
|
skinName,
|
||||||
|
skinVariants,
|
||||||
|
sofficeAvailable
|
||||||
|
} from '../utils/Settings';
|
||||||
|
import plugins from '../../static/js/pluginfw/plugin_defs.js';
|
||||||
|
import log4js from "log4js";
|
||||||
|
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||||
|
import {createCollection} from '../stats';
|
||||||
|
import {strict as assert} from "assert";
|
||||||
|
|
||||||
|
import {RateLimiterMemory} from 'rate-limiter-flexible';
|
||||||
|
import webaccess from '../hooks/express/webaccess';
|
||||||
|
import {ErrorCaused} from "../models/ErrorCaused";
|
||||||
|
import {Pad} from "../db/Pad";
|
||||||
|
import {SessionInfo} from "../models/SessionInfo";
|
||||||
|
|
||||||
const securityManager = require('../db/SecurityManager');
|
const securityManager = require('../db/SecurityManager');
|
||||||
const plugins = require('../../static/js/pluginfw/plugin_defs.js');
|
|
||||||
const log4js = require('log4js');
|
|
||||||
const messageLogger = log4js.getLogger('message');
|
const messageLogger = log4js.getLogger('message');
|
||||||
const accessLogger = log4js.getLogger('access');
|
const accessLogger = log4js.getLogger('access');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
|
||||||
const stats = require('../stats');
|
|
||||||
const assert = require('assert').strict;
|
|
||||||
const {RateLimiterMemory} = require('rate-limiter-flexible');
|
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
|
||||||
|
|
||||||
let rateLimiter;
|
let rateLimiter;
|
||||||
let socketio = null;
|
let socketio = null;
|
||||||
|
|
||||||
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
|
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
|
||||||
|
|
||||||
const addContextToError = (err, pfx) => {
|
const addContextToError = (err: Error, pfx) => {
|
||||||
const newErr = new Error(`${pfx}${err.message}`, {cause: err});
|
const newErr = new ErrorCaused(`${pfx}${err.message}`, err);
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(newErr, addContextToError);
|
if (Error.captureStackTrace) Error.captureStackTrace(newErr, addContextToError);
|
||||||
// Check for https://github.com/tc39/proposal-error-cause support, available in Node.js >= v16.10.
|
// Check for https://github.com/tc39/proposal-error-cause support, available in Node.js >= v16.10.
|
||||||
if (newErr.cause === err) return newErr;
|
if (newErr.cause === err) return newErr;
|
||||||
|
@ -54,11 +82,11 @@ const addContextToError = (err, pfx) => {
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.socketio = () => {
|
export const socketiofn = () => {
|
||||||
// The rate limiter is created in this hook so that restarting the server resets the limiter. The
|
// 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
|
// settings.commitRateLimiting object is passed directly to the rate limiter so that the limits
|
||||||
// can be dynamically changed during runtime by modifying its properties.
|
// can be dynamically changed during runtime by modifying its properties.
|
||||||
rateLimiter = new RateLimiterMemory(settings.commitRateLimiting);
|
rateLimiter = new RateLimiterMemory(commitRateLimiting);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,11 +107,10 @@ exports.socketio = () => {
|
||||||
* - readonly: Whether the client has read-only access (true) or read/write access (false).
|
* - readonly: Whether the client has read-only access (true) or read/write access (false).
|
||||||
* - rev: The last revision that was sent to the client.
|
* - rev: The last revision that was sent to the client.
|
||||||
*/
|
*/
|
||||||
const sessioninfos = {};
|
export const sessioninfos: SessionInfo = {};
|
||||||
exports.sessioninfos = sessioninfos;
|
|
||||||
|
|
||||||
stats.gauge('totalUsers', () => socketio ? Object.keys(socketio.sockets.sockets).length : 0);
|
createCollection.gauge('totalUsers', () => socketio ? Object.keys(socketio.sockets.sockets).length : 0);
|
||||||
stats.gauge('activePads', () => {
|
createCollection.gauge('activePads', () => {
|
||||||
const padIds = new Set();
|
const padIds = new Set();
|
||||||
for (const {padId} of Object.values(sessioninfos)) {
|
for (const {padId} of Object.values(sessioninfos)) {
|
||||||
if (!padId) continue;
|
if (!padId) continue;
|
||||||
|
@ -96,6 +123,8 @@ stats.gauge('activePads', () => {
|
||||||
* Processes one task at a time per channel.
|
* Processes one task at a time per channel.
|
||||||
*/
|
*/
|
||||||
class Channels {
|
class Channels {
|
||||||
|
private readonly _exec: (ch, task) => any;
|
||||||
|
private _promiseChains: Map<any, any>;
|
||||||
/**
|
/**
|
||||||
* @param {(ch, task) => any} [exec] - Task executor. If omitted, tasks are assumed to be
|
* @param {(ch, task) => any} [exec] - Task executor. If omitted, tasks are assumed to be
|
||||||
* functions that will be executed with the channel as the only argument.
|
* functions that will be executed with the channel as the only argument.
|
||||||
|
@ -144,10 +173,14 @@ exports.setSocketIO = (socket_io) => {
|
||||||
* @param socket the socket.io Socket object for the new connection from the client
|
* @param socket the socket.io Socket object for the new connection from the client
|
||||||
*/
|
*/
|
||||||
exports.handleConnect = (socket) => {
|
exports.handleConnect = (socket) => {
|
||||||
stats.meter('connects').mark();
|
createCollection.meter('connects').mark();
|
||||||
|
|
||||||
// Initialize sessioninfos for this new session
|
// Initialize sessioninfos for this new session
|
||||||
sessioninfos[socket.id] = {};
|
sessioninfos[socket.id] = {
|
||||||
|
rev: 0, time: undefined,
|
||||||
|
auth: {padID: undefined, sessionID: undefined, token: undefined},
|
||||||
|
readOnlyPadId: undefined,
|
||||||
|
padId:undefined,readonly:false,author:undefined};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,17 +201,17 @@ exports.kickSessionsFromPad = (padID) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
*/
|
*/
|
||||||
exports.handleDisconnect = async (socket) => {
|
exports.handleDisconnect = async (socket) => {
|
||||||
stats.meter('disconnects').mark();
|
createCollection.meter('disconnects').mark();
|
||||||
const session = sessioninfos[socket.id];
|
const session = sessioninfos[socket.id];
|
||||||
delete sessioninfos[socket.id];
|
delete sessioninfos[socket.id];
|
||||||
// session.padId can be nullish if the user disconnects before sending CLIENT_READY.
|
// session.padId can be nullish if the user disconnects before sending CLIENT_READY.
|
||||||
if (!session || !session.author || !session.padId) return;
|
if (!session || !session.author || !session.padId) return;
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}}: SessionSocketModel = socket.client.request;
|
||||||
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
||||||
accessLogger.info('[LEAVE]' +
|
accessLogger.info('[LEAVE]' +
|
||||||
` pad:${session.padId}` +
|
` pad:${session.padId}` +
|
||||||
` socket:${socket.id}` +
|
` socket:${socket.id}` +
|
||||||
` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
|
` IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
|
||||||
` authorID:${session.author}` +
|
` authorID:${session.author}` +
|
||||||
(user && user.username ? ` username:${user.username}` : ''));
|
(user && user.username ? ` username:${user.username}` : ''));
|
||||||
/* eslint-enable prefer-template */
|
/* eslint-enable prefer-template */
|
||||||
|
@ -187,7 +220,7 @@ exports.handleDisconnect = async (socket) => {
|
||||||
data: {
|
data: {
|
||||||
type: 'USER_LEAVE',
|
type: 'USER_LEAVE',
|
||||||
userInfo: {
|
userInfo: {
|
||||||
colorId: await authorManager.getAuthorColorId(session.author),
|
colorId: await getAuthorColorId(session.author),
|
||||||
userId: session.author,
|
userId: session.author,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -214,7 +247,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
messageLogger.warn(`Rate limited IP ${socket.request.ip}. To reduce the amount of rate ` +
|
messageLogger.warn(`Rate limited IP ${socket.request.ip}. To reduce the amount of rate ` +
|
||||||
'limiting that happens edit the rateLimit values in settings.json');
|
'limiting that happens edit the rateLimit values in settings.json');
|
||||||
stats.meter('rateLimited').mark();
|
createCollection.meter('rateLimited').mark();
|
||||||
socket.json.send({disconnect: 'rateLimited'});
|
socket.json.send({disconnect: 'rateLimited'});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -235,7 +268,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
padID: message.padId,
|
padID: message.padId,
|
||||||
token: message.token,
|
token: message.token,
|
||||||
};
|
};
|
||||||
const padIds = await readOnlyManager.getIds(thisSession.auth.padID);
|
const padIds = await getIds(thisSession.auth.padID);
|
||||||
thisSession.padId = padIds.padId;
|
thisSession.padId = padIds.padId;
|
||||||
thisSession.readOnlyPadId = padIds.readOnlyPadId;
|
thisSession.readOnlyPadId = padIds.readOnlyPadId;
|
||||||
thisSession.readonly =
|
thisSession.readonly =
|
||||||
|
@ -252,12 +285,12 @@ exports.handleMessage = async (socket, message) => {
|
||||||
|
|
||||||
const auth = thisSession.auth;
|
const auth = thisSession.auth;
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
const ip = settings.disableIPlogging ? 'ANONYMOUS' : (socket.request.ip || '<unknown>');
|
const ip = disableIPlogging ? 'ANONYMOUS' : (socket.request.ip || '<unknown>');
|
||||||
const msg = JSON.stringify(message, null, 2);
|
const msg = JSON.stringify(message, null, 2);
|
||||||
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
|
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}}:SessionSocketModel = socket.client.request;
|
||||||
const {accessStatus, authorID} =
|
const {accessStatus, authorID} =
|
||||||
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
|
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
|
||||||
if (accessStatus !== 'grant') {
|
if (accessStatus !== 'grant') {
|
||||||
|
@ -269,7 +302,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
throw new Error([
|
throw new Error([
|
||||||
'Author ID changed mid-session. Bad or missing token or sessionID?',
|
'Author ID changed mid-session. Bad or missing token or sessionID?',
|
||||||
`socket:${socket.id}`,
|
`socket:${socket.id}`,
|
||||||
`IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}`,
|
`IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}`,
|
||||||
`originalAuthorID:${thisSession.author}`,
|
`originalAuthorID:${thisSession.author}`,
|
||||||
`newAuthorID:${authorID}`,
|
`newAuthorID:${authorID}`,
|
||||||
...(user && user.username) ? [`username:${user.username}`] : [],
|
...(user && user.username) ? [`username:${user.username}`] : [],
|
||||||
|
@ -331,7 +364,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'USER_CHANGES':
|
case 'USER_CHANGES':
|
||||||
stats.counter('pendingEdits').inc();
|
createCollection.counter('pendingEdits').inc();
|
||||||
await padChannels.enqueue(thisSession.padId, {socket, message});
|
await padChannels.enqueue(thisSession.padId, {socket, message});
|
||||||
break;
|
break;
|
||||||
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message); break;
|
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message); break;
|
||||||
|
@ -372,7 +405,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
*/
|
*/
|
||||||
const handleSaveRevisionMessage = async (socket, message) => {
|
const handleSaveRevisionMessage = async (socket, message) => {
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await getPad(padId, null, authorId);
|
||||||
await pad.addSavedRevision(pad.head, authorId);
|
await pad.addSavedRevision(pad.head, authorId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -401,7 +434,7 @@ exports.handleCustomObjectMessage = (msg, sessionID) => {
|
||||||
* @param padID {Pad} the pad to which we're sending this message
|
* @param padID {Pad} the pad to which we're sending this message
|
||||||
* @param msgString {String} the message we're sending
|
* @param msgString {String} the message we're sending
|
||||||
*/
|
*/
|
||||||
exports.handleCustomMessage = (padID, msgString) => {
|
export const handleCustomMessage = (padID, msgString) => {
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
const msg = {
|
const msg = {
|
||||||
type: 'COLLABROOM',
|
type: 'COLLABROOM',
|
||||||
|
@ -424,7 +457,7 @@ const handleChatMessage = async (socket, message) => {
|
||||||
// Don't trust the user-supplied values.
|
// Don't trust the user-supplied values.
|
||||||
chatMessage.time = Date.now();
|
chatMessage.time = Date.now();
|
||||||
chatMessage.authorId = authorId;
|
chatMessage.authorId = authorId;
|
||||||
await exports.sendChatMessageToPadClients(chatMessage, padId);
|
await sendChatMessageToPadClients(chatMessage, padId);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -438,15 +471,15 @@ const handleChatMessage = async (socket, message) => {
|
||||||
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
||||||
* object as the first argument and the destination pad ID as the second argument instead.
|
* object as the first argument and the destination pad ID as the second argument instead.
|
||||||
*/
|
*/
|
||||||
exports.sendChatMessageToPadClients = async (mt, puId, text = null, padId = null) => {
|
export const sendChatMessageToPadClients = async (mt, puId, text = null, padId = null) => {
|
||||||
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
||||||
padId = mt instanceof ChatMessage ? puId : padId;
|
padId = mt instanceof ChatMessage ? puId : padId;
|
||||||
const pad = await padManager.getPad(padId, null, message.authorId);
|
const pad = await getPad(padId, null, message.authorId);
|
||||||
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
|
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
|
||||||
// pad.appendChatMessage() ignores the displayName property so we don't need to wait for
|
// 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.
|
// authorManager.getAuthorName() to resolve before saving the message to the database.
|
||||||
const promise = pad.appendChatMessage(message);
|
const promise = pad.appendChatMessage(message);
|
||||||
message.displayName = await authorManager.getAuthorName(message.authorId);
|
message.displayName = await getAuthorName(message.authorId);
|
||||||
socketio.sockets.in(padId).json.send({
|
socketio.sockets.in(padId).json.send({
|
||||||
type: 'COLLABROOM',
|
type: 'COLLABROOM',
|
||||||
data: {type: 'CHAT_MESSAGE', message},
|
data: {type: 'CHAT_MESSAGE', message},
|
||||||
|
@ -465,7 +498,7 @@ const handleGetChatMessages = async (socket, {data: {start, end}}) => {
|
||||||
const count = end - start;
|
const count = end - start;
|
||||||
if (count < 0 || count > 100) throw new Error(`invalid number of messages: ${count}`);
|
if (count < 0 || count > 100) throw new Error(`invalid number of messages: ${count}`);
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await getPad(padId, null, authorId);
|
||||||
|
|
||||||
const chatMessages = await pad.getChatMessages(start, end);
|
const chatMessages = await pad.getChatMessages(start, end);
|
||||||
const infoMsg = {
|
const infoMsg = {
|
||||||
|
@ -517,8 +550,8 @@ const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}})
|
||||||
|
|
||||||
// Tell the authorManager about the new attributes
|
// Tell the authorManager about the new attributes
|
||||||
const p = Promise.all([
|
const p = Promise.all([
|
||||||
authorManager.setAuthorColorId(author, colorId),
|
setAuthorColorId(author, colorId),
|
||||||
authorManager.setAuthorName(author, name),
|
setAuthorName(author, name),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const padId = session.padId;
|
const padId = session.padId;
|
||||||
|
@ -555,7 +588,7 @@ const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}})
|
||||||
*/
|
*/
|
||||||
const handleUserChanges = async (socket, message) => {
|
const handleUserChanges = async (socket, message) => {
|
||||||
// This one's no longer pending, as we're gonna process it now
|
// This one's no longer pending, as we're gonna process it now
|
||||||
stats.counter('pendingEdits').dec();
|
createCollection.counter('pendingEdits').dec();
|
||||||
|
|
||||||
// The client might disconnect between our callbacks. We should still
|
// The client might disconnect between our callbacks. We should still
|
||||||
// finish processing the changeset, so keep a reference to the session.
|
// finish processing the changeset, so keep a reference to the session.
|
||||||
|
@ -567,14 +600,14 @@ const handleUserChanges = async (socket, message) => {
|
||||||
if (!thisSession) throw new Error('client disconnected');
|
if (!thisSession) throw new Error('client disconnected');
|
||||||
|
|
||||||
// Measure time to process edit
|
// Measure time to process edit
|
||||||
const stopWatch = stats.timer('edits').start();
|
const stopWatch = createCollection.timer('edits').start();
|
||||||
try {
|
try {
|
||||||
const {data: {baseRev, apool, changeset}} = message;
|
const {data: {baseRev, apool, changeset}} = message;
|
||||||
if (baseRev == null) throw new Error('missing baseRev');
|
if (baseRev == null) throw new Error('missing baseRev');
|
||||||
if (apool == null) throw new Error('missing apool');
|
if (apool == null) throw new Error('missing apool');
|
||||||
if (changeset == null) throw new Error('missing changeset');
|
if (changeset == null) throw new Error('missing changeset');
|
||||||
const wireApool = (new AttributePool()).fromJsonable(apool);
|
const wireApool = (new AttributePool()).fromJsonable(apool);
|
||||||
const pad = await padManager.getPad(thisSession.padId, null, thisSession.author);
|
const pad = await getPad(thisSession.padId, null, thisSession.author);
|
||||||
|
|
||||||
// Verify that the changeset has valid syntax and is in canonical form
|
// Verify that the changeset has valid syntax and is in canonical form
|
||||||
Changeset.checkRep(changeset);
|
Changeset.checkRep(changeset);
|
||||||
|
@ -654,7 +687,7 @@ const handleUserChanges = async (socket, message) => {
|
||||||
await exports.updatePadClients(pad);
|
await exports.updatePadClients(pad);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
socket.json.send({disconnect: 'badChangeset'});
|
socket.json.send({disconnect: 'badChangeset'});
|
||||||
stats.meter('failedChangesets').mark();
|
createCollection.meter('failedChangesets').mark();
|
||||||
messageLogger.warn(`Failed to apply USER_CHANGES from author ${thisSession.author} ` +
|
messageLogger.warn(`Failed to apply USER_CHANGES from author ${thisSession.author} ` +
|
||||||
`(socket ${socket.id}) on pad ${thisSession.padId}: ${err.stack || err}`);
|
`(socket ${socket.id}) on pad ${thisSession.padId}: ${err.stack || err}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -662,7 +695,7 @@ const handleUserChanges = async (socket, message) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.updatePadClients = async (pad) => {
|
export const updatePadClients = async (pad) => {
|
||||||
// skip this if no-one is on this pad
|
// skip this if no-one is on this pad
|
||||||
const roomSockets = _getRoomSockets(pad.id);
|
const roomSockets = _getRoomSockets(pad.id);
|
||||||
if (roomSockets.length === 0) return;
|
if (roomSockets.length === 0) return;
|
||||||
|
@ -784,13 +817,13 @@ const handleClientReady = async (socket, message) => {
|
||||||
authorColorId = null;
|
authorColorId = null;
|
||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
authorName && authorManager.setAuthorName(sessionInfo.author, authorName),
|
authorName && setAuthorName(sessionInfo.author, authorName),
|
||||||
authorColorId && authorManager.setAuthorColorId(sessionInfo.author, authorColorId),
|
authorColorId && setAuthorColorId(sessionInfo.author, authorColorId),
|
||||||
]);
|
]);
|
||||||
({colorId: authorColorId, name: authorName} = await authorManager.getAuthor(sessionInfo.author));
|
({colorId: authorColorId, name: authorName} = await getAuthor(sessionInfo.author));
|
||||||
|
|
||||||
// load the pad-object from the database
|
// load the pad-object from the database
|
||||||
const pad = await padManager.getPad(sessionInfo.padId, null, sessionInfo.author);
|
const pad = await createCollection.getPad(sessionInfo.padId, null, sessionInfo.author);
|
||||||
|
|
||||||
// these db requests all need the pad object (timestamp of latest revision, author data)
|
// these db requests all need the pad object (timestamp of latest revision, author data)
|
||||||
const authors = pad.getAllAuthors();
|
const authors = pad.getAllAuthors();
|
||||||
|
@ -801,7 +834,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
// get all author data out of the database (in parallel)
|
// get all author data out of the database (in parallel)
|
||||||
const historicalAuthorData = {};
|
const historicalAuthorData = {};
|
||||||
await Promise.all(authors.map(async (authorId) => {
|
await Promise.all(authors.map(async (authorId) => {
|
||||||
const author = await authorManager.getAuthor(authorId);
|
const author = await getAuthor(authorId);
|
||||||
if (!author) {
|
if (!author) {
|
||||||
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
|
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
|
||||||
'This is possibly related to https://github.com/ether/etherpad-lite/issues/2802');
|
'This is possibly related to https://github.com/ether/etherpad-lite/issues/2802');
|
||||||
|
@ -825,18 +858,18 @@ const handleClientReady = async (socket, message) => {
|
||||||
const sinfo = sessioninfos[otherSocket.id];
|
const sinfo = sessioninfos[otherSocket.id];
|
||||||
if (sinfo && sinfo.author === sessionInfo.author) {
|
if (sinfo && sinfo.author === sessionInfo.author) {
|
||||||
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
||||||
sessioninfos[otherSocket.id] = {};
|
sessioninfos[otherSocket.id] = undefined
|
||||||
otherSocket.leave(sessionInfo.padId);
|
otherSocket.leave(sessionInfo.padId);
|
||||||
otherSocket.json.send({disconnect: 'userdup'});
|
otherSocket.json.send({disconnect: 'userdup'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}}:SessionSocketModel = socket.client.request;
|
||||||
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
||||||
accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` +
|
accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` +
|
||||||
` pad:${sessionInfo.padId}` +
|
` pad:${sessionInfo.padId}` +
|
||||||
` socket:${socket.id}` +
|
` socket:${socket.id}` +
|
||||||
` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
|
` IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
|
||||||
` authorID:${sessionInfo.author}` +
|
` authorID:${sessionInfo.author}` +
|
||||||
(user && user.username ? ` username:${user.username}` : ''));
|
(user && user.username ? ` username:${user.username}` : ''));
|
||||||
/* eslint-enable prefer-template */
|
/* eslint-enable prefer-template */
|
||||||
|
@ -922,13 +955,13 @@ const handleClientReady = async (socket, message) => {
|
||||||
// Warning: never ever send sessionInfo.padId to the client. If the client is read only you
|
// Warning: never ever send sessionInfo.padId to the client. If the client is read only you
|
||||||
// would open a security hole 1 swedish mile wide...
|
// would open a security hole 1 swedish mile wide...
|
||||||
const clientVars = {
|
const clientVars = {
|
||||||
skinName: settings.skinName,
|
skinName: skinName,
|
||||||
skinVariants: settings.skinVariants,
|
skinVariants: skinVariants,
|
||||||
randomVersionString: settings.randomVersionString,
|
randomVersionString: randomVersionString,
|
||||||
accountPrivs: {
|
accountPrivs: {
|
||||||
maxRevisions: 100,
|
maxRevisions: 100,
|
||||||
},
|
},
|
||||||
automaticReconnectionTimeout: settings.automaticReconnectionTimeout,
|
automaticReconnectionTimeout: automaticReconnectionTimeout,
|
||||||
initialRevisionList: [],
|
initialRevisionList: [],
|
||||||
initialOptions: {},
|
initialOptions: {},
|
||||||
savedRevisions: pad.getSavedRevisions(),
|
savedRevisions: pad.getSavedRevisions(),
|
||||||
|
@ -941,12 +974,12 @@ const handleClientReady = async (socket, message) => {
|
||||||
rev: pad.getHeadRevisionNumber(),
|
rev: pad.getHeadRevisionNumber(),
|
||||||
time: currentTime,
|
time: currentTime,
|
||||||
},
|
},
|
||||||
colorPalette: authorManager.getColorPalette(),
|
colorPalette: getColorPalette(),
|
||||||
clientIp: '127.0.0.1',
|
clientIp: '127.0.0.1',
|
||||||
userColor: authorColorId,
|
userColor: authorColorId,
|
||||||
padId: sessionInfo.auth.padID,
|
padId: sessionInfo.auth.padID,
|
||||||
padOptions: settings.padOptions,
|
padOptions: padOptions,
|
||||||
padShortcutEnabled: settings.padShortcutEnabled,
|
padShortcutEnabled: padShortcutEnabled,
|
||||||
initialTitle: `Pad: ${sessionInfo.auth.padID}`,
|
initialTitle: `Pad: ${sessionInfo.auth.padID}`,
|
||||||
opts: {},
|
opts: {},
|
||||||
// tell the client the number of the latest chat-message, which will be
|
// tell the client the number of the latest chat-message, which will be
|
||||||
|
@ -956,29 +989,30 @@ const handleClientReady = async (socket, message) => {
|
||||||
readOnlyId: sessionInfo.readOnlyPadId,
|
readOnlyId: sessionInfo.readOnlyPadId,
|
||||||
readonly: sessionInfo.readonly,
|
readonly: sessionInfo.readonly,
|
||||||
serverTimestamp: Date.now(),
|
serverTimestamp: Date.now(),
|
||||||
sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
|
sessionRefreshInterval: cookie.sessionRefreshInterval,
|
||||||
userId: sessionInfo.author,
|
userId: sessionInfo.author,
|
||||||
abiwordAvailable: settings.abiwordAvailable(),
|
abiwordAvailable: abiwordAvailable(),
|
||||||
sofficeAvailable: settings.sofficeAvailable(),
|
sofficeAvailable: sofficeAvailable(),
|
||||||
exportAvailable: settings.exportAvailable(),
|
exportAvailable: exportAvailable(),
|
||||||
plugins: {
|
plugins: {
|
||||||
plugins: plugins.plugins,
|
plugins: plugins.plugins,
|
||||||
parts: plugins.parts,
|
parts: plugins.parts,
|
||||||
},
|
},
|
||||||
indentationOnNewLine: settings.indentationOnNewLine,
|
indentationOnNewLine: indentationOnNewLine,
|
||||||
scrollWhenFocusLineIsOutOfViewport: {
|
scrollWhenFocusLineIsOutOfViewport: {
|
||||||
percentage: {
|
percentage: {
|
||||||
editionAboveViewport:
|
editionAboveViewport:
|
||||||
settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
|
scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
|
||||||
editionBelowViewport:
|
editionBelowViewport:
|
||||||
settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
|
scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
|
||||||
},
|
},
|
||||||
duration: settings.scrollWhenFocusLineIsOutOfViewport.duration,
|
duration: scrollWhenFocusLineIsOutOfViewport.duration,
|
||||||
scrollWhenCaretIsInTheLastLineOfViewport:
|
scrollWhenCaretIsInTheLastLineOfViewport:
|
||||||
settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
|
scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
|
||||||
percentageToScrollWhenUserPressesArrowUp:
|
percentageToScrollWhenUserPressesArrowUp:
|
||||||
settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
|
scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
|
||||||
},
|
},
|
||||||
|
userName: undefined,
|
||||||
initialChangesets: [], // FIXME: REMOVE THIS SHIT
|
initialChangesets: [], // FIXME: REMOVE THIS SHIT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1034,7 +1068,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
if (authorId == null) return;
|
if (authorId == null) return;
|
||||||
|
|
||||||
// reuse previously created cache of author's data
|
// reuse previously created cache of author's data
|
||||||
const authorInfo = historicalAuthorData[authorId] || await authorManager.getAuthor(authorId);
|
const authorInfo = historicalAuthorData[authorId] || await getAuthor(authorId);
|
||||||
if (authorInfo == null) {
|
if (authorInfo == null) {
|
||||||
messageLogger.error(
|
messageLogger.error(
|
||||||
`Author ${authorId} connected via socket.io session ${roomSocket.id} is missing from ` +
|
`Author ${authorId} connected via socket.io session ${roomSocket.id} is missing from ` +
|
||||||
|
@ -1079,7 +1113,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
|
||||||
if (requestID == null) throw new Error('mising requestID');
|
if (requestID == null) throw new Error('mising requestID');
|
||||||
const end = start + (100 * granularity);
|
const end = start + (100 * granularity);
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await createCollection.getPad(padId, null, authorId);
|
||||||
const data = await getChangesetInfo(pad, start, end, granularity);
|
const data = await getChangesetInfo(pad, start, end, granularity);
|
||||||
data.requestID = requestID;
|
data.requestID = requestID;
|
||||||
socket.json.send({type: 'CHANGESET_REQ', data});
|
socket.json.send({type: 'CHANGESET_REQ', data});
|
||||||
|
@ -1089,7 +1123,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
|
||||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||||
*/
|
*/
|
||||||
const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
const getChangesetInfo = async (pad: Pad, startNum: number, endNum: number, granularity: number) => {
|
||||||
const headRevision = pad.getHeadRevisionNumber();
|
const headRevision = pad.getHeadRevisionNumber();
|
||||||
|
|
||||||
// calculate the last full endnum
|
// calculate the last full endnum
|
||||||
|
@ -1120,8 +1154,7 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
||||||
getPadLines(pad, startNum - 1),
|
getPadLines(pad, startNum - 1),
|
||||||
// Get all needed composite Changesets.
|
// Get all needed composite Changesets.
|
||||||
...compositesChangesetNeeded.map(async (item) => {
|
...compositesChangesetNeeded.map(async (item) => {
|
||||||
const changeset = await composePadChangesets(pad, item.start, item.end);
|
composedChangesets[`${item.start}/${item.end}`] = await composePadChangesets(pad, item.start, item.end);
|
||||||
composedChangesets[`${item.start}/${item.end}`] = changeset;
|
|
||||||
}),
|
}),
|
||||||
// Get all needed revision Dates.
|
// Get all needed revision Dates.
|
||||||
...revTimesNeeded.map(async (revNum) => {
|
...revTimesNeeded.map(async (revNum) => {
|
||||||
|
@ -1159,7 +1192,9 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
||||||
|
|
||||||
return {forwardsChangesets, backwardsChangesets,
|
return {forwardsChangesets, backwardsChangesets,
|
||||||
apool: apool.toJsonable(), actualEndNum: endNum,
|
apool: apool.toJsonable(), actualEndNum: endNum,
|
||||||
timeDeltas, start: startNum, granularity};
|
timeDeltas, start: startNum, granularity, requestID: undefined
|
||||||
|
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1238,21 +1273,21 @@ const _getRoomSockets = (padID) => {
|
||||||
/**
|
/**
|
||||||
* Get the number of users in a pad
|
* Get the number of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsersCount = (padID) => ({
|
export const padUsersCount = (padID) => ({
|
||||||
padUsersCount: _getRoomSockets(padID).length,
|
padUsersCount: _getRoomSockets(padID).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of users in a pad
|
* Get the list of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsers = async (padID) => {
|
export const padUsers = async (padID) => {
|
||||||
const padUsers = [];
|
const padUsers = [];
|
||||||
|
|
||||||
// iterate over all clients (in parallel)
|
// iterate over all clients (in parallel)
|
||||||
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
|
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
|
||||||
const s = sessioninfos[roomSocket.id];
|
const s = sessioninfos[roomSocket.id];
|
||||||
if (s) {
|
if (s) {
|
||||||
const author = await authorManager.getAuthor(s.author);
|
const author = await getAuthor(s.author);
|
||||||
// Fixes: https://github.com/ether/etherpad-lite/issues/4120
|
// Fixes: https://github.com/ether/etherpad-lite/issues/4120
|
||||||
// On restart author might not be populated?
|
// On restart author might not be populated?
|
||||||
if (author) {
|
if (author) {
|
||||||
|
@ -1263,6 +1298,4 @@ exports.padUsers = async (padID) => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {padUsers};
|
return {padUsers};
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.sessioninfos = sessioninfos;
|
|
|
@ -20,9 +20,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const settings = require('../utils/Settings');
|
import {disableIPlogging} from "../utils/Settings";
|
||||||
const stats = require('../stats');
|
|
||||||
|
import {createCollection} from '../stats';
|
||||||
|
|
||||||
const logger = log4js.getLogger('socket.io');
|
const logger = log4js.getLogger('socket.io');
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ exports.setSocketIO = (_io) => {
|
||||||
io = _io;
|
io = _io;
|
||||||
|
|
||||||
io.sockets.on('connection', (socket) => {
|
io.sockets.on('connection', (socket) => {
|
||||||
const ip = settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
|
const ip = disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
|
||||||
logger.debug(`${socket.id} connected from IP ${ip}`);
|
logger.debug(`${socket.id} connected from IP ${ip}`);
|
||||||
|
|
||||||
// wrap the original send function to log the messages
|
// wrap the original send function to log the messages
|
||||||
|
@ -68,14 +69,14 @@ exports.setSocketIO = (_io) => {
|
||||||
components[i].handleConnect(socket);
|
components[i].handleConnect(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('message', (message, ack = () => {}) => (async () => {
|
socket.on('message', (message, ack = (p: { name: any; message: any }) => {}) => (async () => {
|
||||||
if (!message.component || !components[message.component]) {
|
if (!message.component || !components[message.component]) {
|
||||||
throw new Error(`unknown message component: ${message.component}`);
|
throw new Error(`unknown message component: ${message.component}`);
|
||||||
}
|
}
|
||||||
logger.debug(`from ${socket.id}:`, message);
|
logger.debug(`from ${socket.id}:`, message);
|
||||||
return await components[message.component].handleMessage(socket, message);
|
return await components[message.component].handleMessage(socket, message);
|
||||||
})().then(
|
})().then(
|
||||||
(val) => ack(null, val),
|
(val) => ack({name: val.name, message: val.message}),
|
||||||
(err) => {
|
(err) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error handling ${message.component} message from ${socket.id}: ${err.stack || err}`);
|
`Error handling ${message.component} message from ${socket.id}: ${err.stack || err}`);
|
||||||
|
@ -88,7 +89,7 @@ exports.setSocketIO = (_io) => {
|
||||||
// when the last user disconnected. If your activePads is 0 and totalUsers is 0
|
// when the last user disconnected. If your activePads is 0 and totalUsers is 0
|
||||||
// you can say, if there has been no active pads or active users for 10 minutes
|
// you can say, if there has been no active pads or active users for 10 minutes
|
||||||
// this instance can be brought out of a scaling cluster.
|
// this instance can be brought out of a scaling cluster.
|
||||||
stats.gauge('lastDisconnect', () => Date.now());
|
createCollection.gauge('lastDisconnect', () => Date.now());
|
||||||
// tell all components about this disconnect
|
// tell all components about this disconnect
|
||||||
for (const i of Object.keys(components)) {
|
for (const i of Object.keys(components)) {
|
||||||
components[i].handleDisconnect(socket);
|
components[i].handleDisconnect(socket);
|
|
@ -1,10 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const eejs = require('../../eejs');
|
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName, args, cb) => {
|
|
||||||
args.app.get('/admin', (req, res) => {
|
|
||||||
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
|
|
||||||
});
|
|
||||||
return cb();
|
|
||||||
};
|
|
10
src/node/hooks/express/admin.ts
Normal file
10
src/node/hooks/express/admin.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
import {required} from '../../eejs';
|
||||||
|
|
||||||
|
export const expressCreateServer = (hookName:string, args, cb) => {
|
||||||
|
args.app.get('/admin', (req, res) => {
|
||||||
|
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||||
|
res.send(required('ep_etherpad-lite/templates/admin/index.html', {req}));
|
||||||
|
});
|
||||||
|
return cb();
|
||||||
|
};
|
|
@ -1,16 +1,21 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const eejs = require('../../eejs');
|
import {required} from '../../eejs';
|
||||||
const settings = require('../../utils/Settings');
|
import {getEpVersion, getGitCommit} from "../../utils/Settings";
|
||||||
const installer = require('../../../static/js/pluginfw/installer');
|
|
||||||
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
|
||||||
const plugins = require('../../../static/js/pluginfw/plugins');
|
|
||||||
const semver = require('semver');
|
|
||||||
const UpdateCheck = require('../../utils/UpdateCheck');
|
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName, args, cb) => {
|
import installer from "../../../static/js/pluginfw/installer";
|
||||||
|
|
||||||
|
import pluginDefs from "../../../static/js/pluginfw/plugin_defs";
|
||||||
|
|
||||||
|
import plugins from "../../../static/js/pluginfw/plugins";
|
||||||
|
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
|
import UpdateCheck from "../../utils/UpdateCheck";
|
||||||
|
|
||||||
|
export const expressCreateServer = (hookName, args, cb) => {
|
||||||
args.app.get('/admin/plugins', (req, res) => {
|
args.app.get('/admin/plugins', (req, res) => {
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
|
res.send(required('ep_etherpad-lite/templates/admin/plugins.html', {
|
||||||
plugins: pluginDefs.plugins,
|
plugins: pluginDefs.plugins,
|
||||||
req,
|
req,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
@ -18,10 +23,10 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
args.app.get('/admin/plugins/info', (req, res) => {
|
args.app.get('/admin/plugins/info', (req, res) => {
|
||||||
const gitCommit = settings.getGitCommit();
|
const gitCommit = getGitCommit();
|
||||||
const epVersion = settings.getEpVersion();
|
const epVersion = getEpVersion();
|
||||||
|
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
res.send(required('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
||||||
gitCommit,
|
gitCommit,
|
||||||
epVersion,
|
epVersion,
|
||||||
installedPlugins: `<pre>${plugins.formatPlugins().replace(/, /g, '\n')}</pre>`,
|
installedPlugins: `<pre>${plugins.formatPlugins().replace(/, /g, '\n')}</pre>`,
|
||||||
|
@ -36,10 +41,10 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
return cb();
|
return cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.socketio = (hookName, args, cb) => {
|
export const socketio = (hookName, args, cb) => {
|
||||||
const io = args.io.of('/pluginfw/installer');
|
const io = args.io.of('/pluginfw/installer');
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
const {session: {user: {is_admin: isAdmin} = {}} = {}}:SessionSocketModel = socket.conn.request;
|
||||||
if (!isAdmin) return;
|
if (!isAdmin) return;
|
||||||
|
|
||||||
socket.on('getInstalled', (query) => {
|
socket.on('getInstalled', (query) => {
|
|
@ -1,14 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const eejs = require('../../eejs');
|
import {required} from '../../eejs';
|
||||||
const fsp = require('fs').promises;
|
import {promises as fsp} from "fs";
|
||||||
const hooks = require('../../../static/js/pluginfw/hooks');
|
|
||||||
const plugins = require('../../../static/js/pluginfw/plugins');
|
import hooks from "../../../static/js/pluginfw/hooks";
|
||||||
const settings = require('../../utils/Settings');
|
|
||||||
|
import plugins from "../../../static/js/pluginfw/plugins";
|
||||||
|
|
||||||
|
import {reloadSettings, settingsFilename, showSettingsInAdminPage} from "../../utils/Settings";
|
||||||
|
import * as settings from "../../utils/Settings";
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName, {app}) => {
|
exports.expressCreateServer = (hookName, {app}) => {
|
||||||
app.get('/admin/settings', (req, res) => {
|
app.get('/admin/settings', (req, res) => {
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
|
res.send(required('ep_etherpad-lite/templates/admin/settings.html', {
|
||||||
req,
|
req,
|
||||||
settings: '',
|
settings: '',
|
||||||
errors: [],
|
errors: [],
|
||||||
|
@ -18,18 +22,20 @@ exports.expressCreateServer = (hookName, {app}) => {
|
||||||
|
|
||||||
exports.socketio = (hookName, {io}) => {
|
exports.socketio = (hookName, {io}) => {
|
||||||
io.of('/settings').on('connection', (socket) => {
|
io.of('/settings').on('connection', (socket) => {
|
||||||
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
const {session: {user: {is_admin: isAdmin} = {}} = {}}:SessionSocketModel = socket.conn.request;
|
||||||
if (!isAdmin) return;
|
if (!isAdmin) return;
|
||||||
|
|
||||||
socket.on('load', async (query) => {
|
socket.on('load', async (query) => {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await fsp.readFile(settings.settingsFilename, 'utf8');
|
data = await fsp.readFile(settingsFilename, 'utf8');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return console.log(err);
|
return console.log(err);
|
||||||
}
|
}
|
||||||
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
|
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
|
||||||
if (settings.showSettingsInAdminPage === false) {
|
//FIXME Is this intentional to never change
|
||||||
|
// @ts-ignore
|
||||||
|
if (showSettingsInAdminPage === false) {
|
||||||
socket.emit('settings', {results: 'NOT_ALLOWED'});
|
socket.emit('settings', {results: 'NOT_ALLOWED'});
|
||||||
} else {
|
} else {
|
||||||
socket.emit('settings', {results: data});
|
socket.emit('settings', {results: data});
|
||||||
|
@ -37,15 +43,15 @@ exports.socketio = (hookName, {io}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('saveSettings', async (newSettings) => {
|
socket.on('saveSettings', async (newSettings) => {
|
||||||
await fsp.writeFile(settings.settingsFilename, newSettings);
|
await fsp.writeFile(settingsFilename, newSettings);
|
||||||
socket.emit('saveprogress', 'saved');
|
socket.emit('saveprogress', 'saved');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('restartServer', async () => {
|
socket.on('restartServer', async () => {
|
||||||
console.log('Admin request to restart server through a socket on /admin/settings');
|
console.log('Admin request to restart server through a socket on /admin/settings');
|
||||||
settings.reloadSettings();
|
reloadSettings();
|
||||||
await plugins.update();
|
await plugins.update();
|
||||||
await hooks.aCallAll('loadSettings', {settings});
|
await hooks.aCallAll('loadSettings', {});
|
||||||
await hooks.aCallAll('restartServer');
|
await hooks.aCallAll('restartServer');
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,12 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const log4js = require('log4js');
|
import log4js from "log4js";
|
||||||
const clientLogger = log4js.getLogger('client');
|
|
||||||
const {Formidable} = require('formidable');
|
|
||||||
const apiHandler = require('../../handler/APIHandler');
|
|
||||||
const util = require('util');
|
|
||||||
|
|
||||||
exports.expressPreSession = async (hookName, {app}) => {
|
import {Formidable} from "formidable";
|
||||||
|
|
||||||
|
import {latestApiVersion} from "../../handler/APIHandler";
|
||||||
|
|
||||||
|
import util from "util";
|
||||||
|
|
||||||
|
const clientLogger = log4js.getLogger('client');
|
||||||
|
export const expressPreSession = async (hookName, {app}) => {
|
||||||
// The Etherpad client side sends information about how a disconnect happened
|
// The Etherpad client side sends information about how a disconnect happened
|
||||||
app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
|
app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
|
||||||
new Formidable().parse(req, (err, fields, files) => {
|
new Formidable().parse(req, (err, fields, files) => {
|
||||||
|
@ -26,7 +29,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
||||||
// The Etherpad client side sends information about client side javscript errors
|
// The Etherpad client side sends information about client side javscript errors
|
||||||
app.post('/jserror', (req, res, next) => {
|
app.post('/jserror', (req, res, next) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const data = JSON.parse(await parseJserrorForm(req));
|
const data = JSON.parse(<string>await parseJserrorForm(req));
|
||||||
clientLogger.warn(`${data.msg} --`, {
|
clientLogger.warn(`${data.msg} --`, {
|
||||||
[util.inspect.custom]: (depth, options) => {
|
[util.inspect.custom]: (depth, options) => {
|
||||||
// Depth is forced to infinity to ensure that all of the provided data is logged.
|
// Depth is forced to infinity to ensure that all of the provided data is logged.
|
||||||
|
@ -40,6 +43,6 @@ exports.expressPreSession = async (hookName, {app}) => {
|
||||||
|
|
||||||
// Provide a possibility to query the latest available API version
|
// Provide a possibility to query the latest available API version
|
||||||
app.get('/api', (req, res) => {
|
app.get('/api', (req, res) => {
|
||||||
res.json({currentVersion: apiHandler.latestApiVersion});
|
res.json({currentVersion: latestApiVersion});
|
||||||
});
|
});
|
||||||
};
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const stats = require('../../stats');
|
import {createCollection} from '../../stats';
|
||||||
|
|
||||||
exports.expressCreateServer = (hook_name, args, cb) => {
|
exports.expressCreateServer = (hook_name, args, cb) => {
|
||||||
exports.app = args.app;
|
exports.app = args.app;
|
||||||
|
@ -12,7 +12,7 @@ exports.expressCreateServer = (hook_name, args, cb) => {
|
||||||
// allowing you to respond however you like
|
// allowing you to respond however you like
|
||||||
res.status(500).send({error: 'Sorry, something bad happened!'});
|
res.status(500).send({error: 'Sorry, something bad happened!'});
|
||||||
console.error(err.stack ? err.stack : err.toString());
|
console.error(err.stack ? err.stack : err.toString());
|
||||||
stats.meter('http500').mark();
|
createCollection.meter('http500').mark();
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb();
|
return cb();
|
|
@ -1,23 +1,23 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const hasPadAccess = require('../../padaccess');
|
import hasPadAccess from '../../padaccess';
|
||||||
const settings = require('../../utils/Settings');
|
import {exportAvailable, importExportRateLimiting} from '../../utils/Settings';
|
||||||
const exportHandler = require('../../handler/ExportHandler');
|
import {doExport} from '../../handler/ExportHandler';
|
||||||
const importHandler = require('../../handler/ImportHandler');
|
import {doImport2} from '../../handler/ImportHandler';
|
||||||
const padManager = require('../../db/PadManager');
|
import {doesPadExist} from '../../db/PadManager';
|
||||||
const readOnlyManager = require('../../db/ReadOnlyManager');
|
import {getPadId, isReadOnlyId} from '../../db/ReadOnlyManager';
|
||||||
const rateLimit = require('express-rate-limit');
|
import rateLimit from 'express-rate-limit';
|
||||||
const securityManager = require('../../db/SecurityManager');
|
import {checkAccess} from '../../db/SecurityManager';
|
||||||
const webaccess = require('./webaccess');
|
import webaccess from './webaccess';
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName, args, cb) => {
|
exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
settings.importExportRateLimiting.onLimitReached = (req, res, options) => {
|
importExportRateLimiting.onLimitReached = (req, res, options) => {
|
||||||
// when the rate limiter triggers, write a warning in the logs
|
// when the rate limiter triggers, write a warning in the logs
|
||||||
console.warn('Import/Export rate limiter triggered on ' +
|
console.warn('Import/Export rate limiter triggered on ' +
|
||||||
`"${req.originalUrl}" for IP address ${req.ip}`);
|
`"${req.originalUrl}" for IP address ${req.ip}`);
|
||||||
};
|
};
|
||||||
// The rate limiter is created in this hook so that restarting the server resets the limiter.
|
// The rate limiter is created in this hook so that restarting the server resets the limiter.
|
||||||
const limiter = rateLimit(settings.importExportRateLimiting);
|
const limiter = rateLimit(importExportRateLimiting);
|
||||||
|
|
||||||
// handle export requests
|
// handle export requests
|
||||||
args.app.use('/p/:pad/:rev?/export/:type', limiter);
|
args.app.use('/p/:pad/:rev?/export/:type', limiter);
|
||||||
|
@ -30,7 +30,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||||
if (settings.exportAvailable() === 'no' &&
|
if (exportAvailable() === 'no' &&
|
||||||
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
||||||
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
|
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
|
||||||
' There is no converter configured');
|
' There is no converter configured');
|
||||||
|
@ -48,19 +48,19 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
let padId = req.params.pad;
|
let padId = req.params.pad;
|
||||||
|
|
||||||
let readOnlyId = null;
|
let readOnlyId = null;
|
||||||
if (readOnlyManager.isReadOnlyId(padId)) {
|
if (isReadOnlyId(padId)) {
|
||||||
readOnlyId = padId;
|
readOnlyId = padId;
|
||||||
padId = await readOnlyManager.getPadId(readOnlyId);
|
padId = await getPadId(readOnlyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = await padManager.doesPadExists(padId);
|
const exists = await doesPadExist(padId);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
|
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
|
||||||
await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
|
await doExport(req, res, padId, readOnlyId, req.params.type);
|
||||||
}
|
}
|
||||||
})().catch((err) => next(err || new Error(err)));
|
})().catch((err) => next(err || new Error(err)));
|
||||||
});
|
});
|
||||||
|
@ -69,13 +69,13 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
||||||
args.app.use('/p/:pad/import', limiter);
|
args.app.use('/p/:pad/import', limiter);
|
||||||
args.app.post('/p/:pad/import', (req, res, next) => {
|
args.app.post('/p/:pad/import', (req, res, next) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const {session: {user} = {}} = req;
|
const {session: {user} = {}}:SessionSocketModel = req;
|
||||||
const {accessStatus, authorID: authorId} = await securityManager.checkAccess(
|
const {accessStatus, authorID: authorId} = await checkAccess(
|
||||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||||
if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) {
|
if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) {
|
||||||
return res.status(403).send('Forbidden');
|
return res.status(403).send('Forbidden');
|
||||||
}
|
}
|
||||||
await importHandler.doImport(req, res, req.params.pad, authorId);
|
await doImport2(req, res, req.params.pad, authorId);
|
||||||
})().catch((err) => next(err || new Error(err)));
|
})().catch((err) => next(err || new Error(err)));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import {ResponseSpec} from "../../models/ResponseSpec";
|
||||||
|
import {APIResource} from "../../models/APIResource";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* node/hooks/express/openapi.js
|
* node/hooks/express/openapi.js
|
||||||
*
|
*
|
||||||
|
@ -54,7 +57,7 @@ const APIPathStyle = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// API resources - describe your API endpoints here
|
// API resources - describe your API endpoints here
|
||||||
const resources = {
|
const resources: APIResource = {
|
||||||
// Group
|
// Group
|
||||||
group: {
|
group: {
|
||||||
create: {
|
create: {
|
||||||
|
@ -375,7 +378,8 @@ const defaultResponses = {
|
||||||
|
|
||||||
const defaultResponseRefs = {
|
const defaultResponseRefs = {
|
||||||
200: {
|
200: {
|
||||||
$ref: '#/components/responses/Success',
|
$ref: '#/components/responses/Success', content: undefined
|
||||||
|
|
||||||
},
|
},
|
||||||
400: {
|
400: {
|
||||||
$ref: '#/components/responses/ApiError',
|
$ref: '#/components/responses/ApiError',
|
||||||
|
@ -491,7 +495,11 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
||||||
|
|
||||||
// build operations
|
// build operations
|
||||||
for (const funcName of Object.keys(apiHandler.version[version])) {
|
for (const funcName of Object.keys(apiHandler.version[version])) {
|
||||||
let operation = {};
|
let operation: ResponseSpec = {
|
||||||
|
parameters: undefined,
|
||||||
|
_restPath: "",
|
||||||
|
operationId: undefined
|
||||||
|
};
|
||||||
if (operations[funcName]) {
|
if (operations[funcName]) {
|
||||||
operation = {...operations[funcName]};
|
operation = {...operations[funcName]};
|
||||||
} else {
|
} else {
|
48
src/node/models/APIResource.ts
Normal file
48
src/node/models/APIResource.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import {ResponseSpec} from "./ResponseSpec";
|
||||||
|
|
||||||
|
export type APIResource = {
|
||||||
|
group:{
|
||||||
|
create:ResponseSpec,
|
||||||
|
createIfNotExistsFor: ResponseSpec,
|
||||||
|
delete: ResponseSpec,
|
||||||
|
listPads: ResponseSpec,
|
||||||
|
createPad: ResponseSpec,
|
||||||
|
listSessions: ResponseSpec,
|
||||||
|
list: ResponseSpec,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
create: ResponseSpec,
|
||||||
|
createIfNotExistsFor: ResponseSpec,
|
||||||
|
listPads: ResponseSpec,
|
||||||
|
listSessions: ResponseSpec,
|
||||||
|
getName: ResponseSpec,
|
||||||
|
},
|
||||||
|
session:{
|
||||||
|
create: ResponseSpec,
|
||||||
|
delete: ResponseSpec,
|
||||||
|
info: ResponseSpec,
|
||||||
|
},
|
||||||
|
pad:{
|
||||||
|
listAll: ResponseSpec,
|
||||||
|
createDiffHTML: ResponseSpec,
|
||||||
|
create: ResponseSpec,
|
||||||
|
getText: ResponseSpec,
|
||||||
|
setText: ResponseSpec,
|
||||||
|
getHTML: ResponseSpec,
|
||||||
|
setHTML: ResponseSpec,
|
||||||
|
getRevisionsCount: ResponseSpec,
|
||||||
|
getLastEdited: ResponseSpec,
|
||||||
|
delete: ResponseSpec,
|
||||||
|
getReadOnlyID: ResponseSpec,
|
||||||
|
setPublicStatus: ResponseSpec,
|
||||||
|
getPublicStatus: ResponseSpec,
|
||||||
|
authors: ResponseSpec,
|
||||||
|
usersCount: ResponseSpec,
|
||||||
|
users: ResponseSpec,
|
||||||
|
sendClientsMessage: ResponseSpec,
|
||||||
|
checkToken: ResponseSpec,
|
||||||
|
getChatHistory: ResponseSpec,
|
||||||
|
getChatHead: ResponseSpec,
|
||||||
|
appendChatMessage: ResponseSpec,
|
||||||
|
}
|
||||||
|
}
|
14
src/node/models/ErrorCaused.ts
Normal file
14
src/node/models/ErrorCaused.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import ts from "typescript/lib/tsserverlibrary";
|
||||||
|
|
||||||
|
export class ErrorCaused extends Error{
|
||||||
|
cause: Error;
|
||||||
|
constructor(message: string, cause: Error) {
|
||||||
|
super(message);
|
||||||
|
this.cause = cause;
|
||||||
|
this.name = "ErrorCaused";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorCause = {
|
||||||
|
|
||||||
|
}
|
6
src/node/models/Plugin.ts
Normal file
6
src/node/models/Plugin.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export type Plugin = {
|
||||||
|
package: {
|
||||||
|
name: string,
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
}
|
43
src/node/models/ResponseSpec.ts
Normal file
43
src/node/models/ResponseSpec.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
export type ResponseSpec = {
|
||||||
|
parameters?: ParameterSpec[],
|
||||||
|
_restPath?: string,
|
||||||
|
operationId?: string,
|
||||||
|
responses?: any
|
||||||
|
description?: string,
|
||||||
|
summary?: string,
|
||||||
|
responseSchema?:{
|
||||||
|
groupID?: APIResponseSpecType
|
||||||
|
groupIDs?: APIResponseSpecType
|
||||||
|
padIDs?: APIResponseSpecType,
|
||||||
|
sessions?: APIResponseSpecType,
|
||||||
|
authorID?: APIResponseSpecType,
|
||||||
|
info?: APIResponseSpecType,
|
||||||
|
sessionID?: APIResponseSpecType,
|
||||||
|
text?: APIResponseSpecType,
|
||||||
|
html?: APIResponseSpecType,
|
||||||
|
revisions?: APIResponseSpecType,
|
||||||
|
lastEdited?: APIResponseSpecType,
|
||||||
|
readOnlyID?: APIResponseSpecType,
|
||||||
|
publicStatus?: APIResponseSpecType,
|
||||||
|
authorIDs?: APIResponseSpecType,
|
||||||
|
padUsersCount?: APIResponseSpecType,
|
||||||
|
padUsers?: APIResponseSpecType,
|
||||||
|
messages?: APIResponseSpecType,
|
||||||
|
chatHead?: APIResponseSpecType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type APIResponseSpecType = {
|
||||||
|
type?:string,
|
||||||
|
items?: {
|
||||||
|
type?:string,
|
||||||
|
$ref?:string
|
||||||
|
},
|
||||||
|
$ref?:string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type ParameterSpec = {
|
||||||
|
$ref?: string,
|
||||||
|
}
|
7
src/node/models/Revision.ts
Normal file
7
src/node/models/Revision.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export type Revision = {
|
||||||
|
revNum: number,
|
||||||
|
savedById: string,
|
||||||
|
label: string,
|
||||||
|
timestamp: number,
|
||||||
|
id: string,
|
||||||
|
}
|
10
src/node/models/SessionInfo.ts
Normal file
10
src/node/models/SessionInfo.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export type SessionInfo = {
|
||||||
|
[key: string]:{
|
||||||
|
time: any;
|
||||||
|
rev: number;
|
||||||
|
readOnlyPadId: any;
|
||||||
|
auth: { padID: any; sessionID: any; token: any };
|
||||||
|
readonly: boolean;
|
||||||
|
padId: string,
|
||||||
|
author: string
|
||||||
|
}}
|
5
src/node/models/SessionModel.ts
Normal file
5
src/node/models/SessionModel.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export type SessionModel = {
|
||||||
|
cookie: {
|
||||||
|
expires?: string
|
||||||
|
}
|
||||||
|
}
|
8
src/node/models/SessionSocketModel.ts
Normal file
8
src/node/models/SessionSocketModel.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
type SessionSocketModel = {
|
||||||
|
session:{
|
||||||
|
user?: {
|
||||||
|
username?: string,
|
||||||
|
is_admin?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
const securityManager = require('./db/SecurityManager');
|
import {checkAccess} from './db/SecurityManager';
|
||||||
|
|
||||||
// checks for padAccess
|
// checks for padAccess
|
||||||
module.exports = async (req, res) => {
|
export default async (req, res) => {
|
||||||
const {session: {user} = {}} = req;
|
const {session: {user} = {}}:SessionSocketModel = req;
|
||||||
const accessObj = await securityManager.checkAccess(
|
const accessObj = await checkAccess(
|
||||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||||
|
|
||||||
if (accessObj.accessStatus === 'grant') {
|
if (accessObj.accessStatus === 'grant') {
|
81
src/node/server.js → src/node/server.ts
Executable file → Normal file
81
src/node/server.js → src/node/server.ts
Executable file → Normal file
|
@ -24,10 +24,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
log4js.replaceConsole();
|
import * as settings from "./utils/Settings";
|
||||||
|
/*
|
||||||
const settings = require('./utils/Settings');
|
* early check for version compatibility before calling
|
||||||
|
* any modules that require newer versions of NodeJS
|
||||||
|
*/
|
||||||
|
import {checkDeprecationStatus, enforceMinNodeVersion} from './utils/NodeVersion'
|
||||||
|
import {Gate} from './utils/promises';
|
||||||
|
import * as UpdateCheck from "./utils/UpdateCheck";
|
||||||
|
import {Plugin} from "./models/Plugin";
|
||||||
|
|
||||||
let wtfnode;
|
let wtfnode;
|
||||||
if (settings.dumpOnUncleanExit) {
|
if (settings.dumpOnUncleanExit) {
|
||||||
|
@ -36,24 +42,20 @@ if (settings.dumpOnUncleanExit) {
|
||||||
wtfnode = require('wtfnode');
|
wtfnode = require('wtfnode');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
enforceMinNodeVersion('12.17.0');
|
||||||
* early check for version compatibility before calling
|
checkDeprecationStatus('12.17.0', '1.9.0');
|
||||||
* any modules that require newer versions of NodeJS
|
|
||||||
*/
|
|
||||||
const NodeVersion = require('./utils/NodeVersion');
|
|
||||||
NodeVersion.enforceMinNodeVersion('12.17.0');
|
|
||||||
NodeVersion.checkDeprecationStatus('12.17.0', '1.9.0');
|
|
||||||
|
|
||||||
const UpdateCheck = require('./utils/UpdateCheck');
|
import db = require('./db/DB');
|
||||||
const db = require('./db/DB');
|
import {} from './db/DB'
|
||||||
const express = require('./hooks/express');
|
import express = require('./hooks/express');
|
||||||
const hooks = require('../static/js/pluginfw/hooks');
|
import hooks = require('../static/js/pluginfw/hooks');
|
||||||
const pluginDefs = require('../static/js/pluginfw/plugin_defs');
|
import pluginDefs = require('../static/js/pluginfw/plugin_defs');
|
||||||
const plugins = require('../static/js/pluginfw/plugins');
|
import plugins = require('../static/js/pluginfw/plugins');
|
||||||
const {Gate} = require('./utils/promises');
|
import stats = require('./stats');
|
||||||
const stats = require('./stats');
|
import {createCollection} from "./stats";
|
||||||
|
|
||||||
const logger = log4js.getLogger('server');
|
const logger = log4js.getLogger('server');
|
||||||
|
console.log = logger.info.bind(logger); // do the same for others - console.debug, etc.
|
||||||
|
|
||||||
const State = {
|
const State = {
|
||||||
INITIAL: 1,
|
INITIAL: 1,
|
||||||
|
@ -76,14 +78,14 @@ const removeSignalListener = (signal, listener) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let startDoneGate;
|
let startDoneGate;
|
||||||
exports.start = async () => {
|
export const start = async () => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State.INITIAL:
|
case State.INITIAL:
|
||||||
break;
|
break;
|
||||||
case State.STARTING:
|
case State.STARTING:
|
||||||
await startDoneGate;
|
await startDoneGate;
|
||||||
// Retry. Don't fall through because it might have transitioned to STATE_TRANSITION_FAILED.
|
// Retry. Don't fall through because it might have transitioned to STATE_TRANSITION_FAILED.
|
||||||
return await exports.start();
|
return await start();
|
||||||
case State.RUNNING:
|
case State.RUNNING:
|
||||||
return express.server;
|
return express.server;
|
||||||
case State.STOPPING:
|
case State.STOPPING:
|
||||||
|
@ -100,16 +102,16 @@ exports.start = async () => {
|
||||||
state = State.STARTING;
|
state = State.STARTING;
|
||||||
try {
|
try {
|
||||||
// Check if Etherpad version is up-to-date
|
// Check if Etherpad version is up-to-date
|
||||||
UpdateCheck.check();
|
UpdateCheck.default.check();
|
||||||
|
|
||||||
stats.gauge('memoryUsage', () => process.memoryUsage().rss);
|
createCollection.gauge('memoryUsage', () => process.memoryUsage().rss);
|
||||||
stats.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed);
|
createCollection.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed);
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
logger.debug(`uncaught exception: ${err.stack || err}`);
|
logger.debug(`uncaught exception: ${err.stack || err}`);
|
||||||
|
|
||||||
// eslint-disable-next-line promise/no-promise-in-callback
|
// eslint-disable-next-line promise/no-promise-in-callback
|
||||||
exports.exit(err)
|
exit(err)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error('Error in process exit', err);
|
logger.error('Error in process exit', err);
|
||||||
// eslint-disable-next-line n/no-process-exit
|
// eslint-disable-next-line n/no-process-exit
|
||||||
|
@ -118,7 +120,8 @@ exports.start = async () => {
|
||||||
});
|
});
|
||||||
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
|
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
|
||||||
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
|
||||||
process.on('unhandledRejection', (err) => {
|
process.on('unhandledRejection', (err:Error) => {
|
||||||
|
|
||||||
logger.debug(`unhandled rejection: ${err.stack || err}`);
|
logger.debug(`unhandled rejection: ${err.stack || err}`);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
@ -128,10 +131,10 @@ exports.start = async () => {
|
||||||
// done cleaning up. See https://github.com/andywer/threads.js/pull/329 for an example of a
|
// done cleaning up. See https://github.com/andywer/threads.js/pull/329 for an example of a
|
||||||
// problematic listener. This means that exports.exit is solely responsible for performing all
|
// problematic listener. This means that exports.exit is solely responsible for performing all
|
||||||
// necessary cleanup tasks.
|
// necessary cleanup tasks.
|
||||||
for (const listener of process.listeners(signal)) {
|
for (const listener of process.listeners(signal as any)) {
|
||||||
removeSignalListener(signal, listener);
|
removeSignalListener(signal, listener);
|
||||||
}
|
}
|
||||||
process.on(signal, exports.exit);
|
process.on(signal, exit);
|
||||||
// Prevent signal listeners from being added in the future.
|
// Prevent signal listeners from being added in the future.
|
||||||
process.on('newListener', (event, listener) => {
|
process.on('newListener', (event, listener) => {
|
||||||
if (event !== signal) return;
|
if (event !== signal) return;
|
||||||
|
@ -141,7 +144,7 @@ exports.start = async () => {
|
||||||
|
|
||||||
await db.init();
|
await db.init();
|
||||||
await plugins.update();
|
await plugins.update();
|
||||||
const installedPlugins = Object.values(pluginDefs.plugins)
|
const installedPlugins = (Object.values(pluginDefs.plugins) as Plugin[])
|
||||||
.filter((plugin) => plugin.package.name !== 'ep_etherpad-lite')
|
.filter((plugin) => plugin.package.name !== 'ep_etherpad-lite')
|
||||||
.map((plugin) => `${plugin.package.name}@${plugin.package.version}`)
|
.map((plugin) => `${plugin.package.name}@${plugin.package.version}`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
@ -154,7 +157,7 @@ exports.start = async () => {
|
||||||
logger.error('Error occurred while starting Etherpad');
|
logger.error('Error occurred while starting Etherpad');
|
||||||
state = State.STATE_TRANSITION_FAILED;
|
state = State.STATE_TRANSITION_FAILED;
|
||||||
startDoneGate.resolve();
|
startDoneGate.resolve();
|
||||||
return await exports.exit(err);
|
return await exit(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Etherpad is running');
|
logger.info('Etherpad is running');
|
||||||
|
@ -166,12 +169,12 @@ exports.start = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopDoneGate = new Gate();
|
const stopDoneGate = new Gate();
|
||||||
exports.stop = async () => {
|
export const stop = async () => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State.STARTING:
|
case State.STARTING:
|
||||||
await exports.start();
|
await start();
|
||||||
// Don't fall through to State.RUNNING in case another caller is also waiting for startup.
|
// Don't fall through to State.RUNNING in case another caller is also waiting for startup.
|
||||||
return await exports.stop();
|
return await stop();
|
||||||
case State.RUNNING:
|
case State.RUNNING:
|
||||||
break;
|
break;
|
||||||
case State.STOPPING:
|
case State.STOPPING:
|
||||||
|
@ -201,7 +204,7 @@ exports.stop = async () => {
|
||||||
logger.error('Error occurred while stopping Etherpad');
|
logger.error('Error occurred while stopping Etherpad');
|
||||||
state = State.STATE_TRANSITION_FAILED;
|
state = State.STATE_TRANSITION_FAILED;
|
||||||
stopDoneGate.resolve();
|
stopDoneGate.resolve();
|
||||||
return await exports.exit(err);
|
return await exit(err);
|
||||||
}
|
}
|
||||||
logger.info('Etherpad stopped');
|
logger.info('Etherpad stopped');
|
||||||
state = State.STOPPED;
|
state = State.STOPPED;
|
||||||
|
@ -210,14 +213,14 @@ exports.stop = async () => {
|
||||||
|
|
||||||
let exitGate;
|
let exitGate;
|
||||||
let exitCalled = false;
|
let exitCalled = false;
|
||||||
exports.exit = async (err = null) => {
|
export const exit = async (err = null) => {
|
||||||
/* eslint-disable no-process-exit */
|
/* eslint-disable no-process-exit */
|
||||||
if (err === 'SIGTERM') {
|
if (err === 'SIGTERM') {
|
||||||
// Termination from SIGTERM is not treated as an abnormal termination.
|
// Termination from SIGTERM is not treated as an abnormal termination.
|
||||||
logger.info('Received SIGTERM signal');
|
logger.info('Received SIGTERM signal');
|
||||||
err = null;
|
err = null;
|
||||||
} else if (err != null) {
|
} else if (err != null) {
|
||||||
logger.error(`Metrics at time of fatal error:\n${JSON.stringify(stats.toJSON(), null, 2)}`);
|
logger.error(`Metrics at time of fatal error:\n${JSON.stringify(createCollection.toJSON(), null, 2)}`);
|
||||||
logger.error(err.stack || err.toString());
|
logger.error(err.stack || err.toString());
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
if (exitCalled) {
|
if (exitCalled) {
|
||||||
|
@ -231,11 +234,11 @@ exports.exit = async (err = null) => {
|
||||||
case State.STARTING:
|
case State.STARTING:
|
||||||
case State.RUNNING:
|
case State.RUNNING:
|
||||||
case State.STOPPING:
|
case State.STOPPING:
|
||||||
await exports.stop();
|
await stop();
|
||||||
// Don't fall through to State.STOPPED in case another caller is also waiting for stop().
|
// Don't fall through to State.STOPPED in case another caller is also waiting for stop().
|
||||||
// Don't pass err to exports.exit() because this err has already been processed. (If err is
|
// Don't pass err to exports.exit() because this err has already been processed. (If err is
|
||||||
// passed again to exit() then exit() will think that a second error occurred while exiting.)
|
// passed again to exit() then exit() will think that a second error occurred while exiting.)
|
||||||
return await exports.exit();
|
return await exit();
|
||||||
case State.INITIAL:
|
case State.INITIAL:
|
||||||
case State.STOPPED:
|
case State.STOPPED:
|
||||||
case State.STATE_TRANSITION_FAILED:
|
case State.STATE_TRANSITION_FAILED:
|
||||||
|
@ -275,4 +278,4 @@ exports.exit = async (err = null) => {
|
||||||
/* eslint-enable no-process-exit */
|
/* eslint-enable no-process-exit */
|
||||||
};
|
};
|
||||||
|
|
||||||
if (require.main === module) exports.start();
|
if (require.main === module) start();
|
|
@ -1,9 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const measured = require('measured-core');
|
|
||||||
|
|
||||||
module.exports = measured.createCollection();
|
|
||||||
|
|
||||||
module.exports.shutdown = async (hookName, context) => {
|
|
||||||
module.exports.end();
|
|
||||||
};
|
|
9
src/node/stats.ts
Normal file
9
src/node/stats.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import measured from 'measured-core'
|
||||||
|
|
||||||
|
export const createCollection = measured.createCollection();
|
||||||
|
|
||||||
|
export const shutdown = async (hookName, context) => {
|
||||||
|
module.exports.end();
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ const semver = require('semver');
|
||||||
*
|
*
|
||||||
* @param {String} minNodeVersion Minimum required Node version
|
* @param {String} minNodeVersion Minimum required Node version
|
||||||
*/
|
*/
|
||||||
exports.enforceMinNodeVersion = (minNodeVersion) => {
|
const enforceMinNodeVersion = (minNodeVersion:string) => {
|
||||||
const currentNodeVersion = process.version;
|
const currentNodeVersion = process.version;
|
||||||
|
|
||||||
// we cannot use template literals, since we still do not know if we are
|
// we cannot use template literals, since we still do not know if we are
|
||||||
|
@ -49,7 +49,7 @@ exports.enforceMinNodeVersion = (minNodeVersion) => {
|
||||||
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated
|
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated
|
||||||
* Node releases
|
* Node releases
|
||||||
*/
|
*/
|
||||||
exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion, epRemovalVersion) => {
|
const checkDeprecationStatus = (lowestNonDeprecatedNodeVersion:string, epRemovalVersion:string) => {
|
||||||
const currentNodeVersion = process.version;
|
const currentNodeVersion = process.version;
|
||||||
|
|
||||||
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {
|
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {
|
||||||
|
@ -58,3 +58,5 @@ exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion, epRemovalVersi
|
||||||
`Please consider updating at least to Node ${lowestNonDeprecatedNodeVersion}`);
|
`Please consider updating at least to Node ${lowestNonDeprecatedNodeVersion}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export {checkDeprecationStatus, enforceMinNodeVersion}
|
|
@ -29,12 +29,12 @@
|
||||||
|
|
||||||
const absolutePaths = require('./AbsolutePaths');
|
const absolutePaths = require('./AbsolutePaths');
|
||||||
const deepEqual = require('fast-deep-equal/es6');
|
const deepEqual = require('fast-deep-equal/es6');
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const os = require('os');
|
import os from 'os';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const argv = require('./Cli').argv;
|
const argv = require('./Cli').argv;
|
||||||
const jsonminify = require('jsonminify');
|
import jsonminify from 'jsonminify';
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const randomString = require('./randomstring');
|
const randomString = require('./randomstring');
|
||||||
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
|
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
|
||||||
'suppressErrorsInPadText to true in your settings.json\n';
|
'suppressErrorsInPadText to true in your settings.json\n';
|
||||||
|
@ -57,8 +57,8 @@ const initLogging = (logLevel, config) => {
|
||||||
// log4js.configure() modifies exports.logconfig so check for equality first.
|
// log4js.configure() modifies exports.logconfig so check for equality first.
|
||||||
const logConfigIsDefault = deepEqual(config, defaultLogConfig());
|
const logConfigIsDefault = deepEqual(config, defaultLogConfig());
|
||||||
log4js.configure(config);
|
log4js.configure(config);
|
||||||
log4js.setGlobalLogLevel(logLevel);
|
log4js.getLogger("console");
|
||||||
log4js.replaceConsole();
|
console.log = logger.info.bind(logger)
|
||||||
// Log the warning after configuring log4js to increase the chances the user will see it.
|
// Log the warning after configuring log4js to increase the chances the user will see it.
|
||||||
if (!logConfigIsDefault) logger.warn('The logconfig setting is deprecated.');
|
if (!logConfigIsDefault) logger.warn('The logconfig setting is deprecated.');
|
||||||
};
|
};
|
||||||
|
@ -68,16 +68,16 @@ const initLogging = (logLevel, config) => {
|
||||||
initLogging(defaultLogLevel, defaultLogConfig());
|
initLogging(defaultLogLevel, defaultLogConfig());
|
||||||
|
|
||||||
/* Root path of the installation */
|
/* Root path of the installation */
|
||||||
exports.root = absolutePaths.findEtherpadRoot();
|
export const root = absolutePaths.findEtherpadRoot();
|
||||||
logger.info('All relative paths will be interpreted relative to the identified ' +
|
logger.info('All relative paths will be interpreted relative to the identified ' +
|
||||||
`Etherpad base dir: ${exports.root}`);
|
`Etherpad base dir: ${exports.root}`);
|
||||||
exports.settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
|
export const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
|
||||||
exports.credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
|
export const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app title, visible e.g. in the browser window
|
* The app title, visible e.g. in the browser window
|
||||||
*/
|
*/
|
||||||
exports.title = 'Etherpad';
|
export const title = 'Etherpad';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pathname of the favicon you want to use. If null, the skin's favicon is
|
* Pathname of the favicon you want to use. If null, the skin's favicon is
|
||||||
|
@ -85,7 +85,7 @@ exports.title = 'Etherpad';
|
||||||
* is used. If this is a relative path it is interpreted as relative to the
|
* is used. If this is a relative path it is interpreted as relative to the
|
||||||
* Etherpad root directory.
|
* Etherpad root directory.
|
||||||
*/
|
*/
|
||||||
exports.favicon = null;
|
export const favicon = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Skin name.
|
* Skin name.
|
||||||
|
@ -93,37 +93,37 @@ exports.favicon = null;
|
||||||
* Initialized to null, so we can spot an old configuration file and invite the
|
* Initialized to null, so we can spot an old configuration file and invite the
|
||||||
* user to update it before falling back to the default.
|
* user to update it before falling back to the default.
|
||||||
*/
|
*/
|
||||||
exports.skinName = null;
|
export const skinName = null;
|
||||||
|
|
||||||
exports.skinVariants = 'super-light-toolbar super-light-editor light-background';
|
export const skinVariants = 'super-light-toolbar super-light-editor light-background';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IP ep-lite should listen to
|
* The IP ep-lite should listen to
|
||||||
*/
|
*/
|
||||||
exports.ip = '0.0.0.0';
|
export const ip = '0.0.0.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Port ep-lite should listen to
|
* The Port ep-lite should listen to
|
||||||
*/
|
*/
|
||||||
exports.port = process.env.PORT || 9001;
|
export const port = process.env.PORT || 9001;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should we suppress Error messages from being in Pad Contents
|
* Should we suppress Error messages from being in Pad Contents
|
||||||
*/
|
*/
|
||||||
exports.suppressErrorsInPadText = false;
|
export const suppressErrorsInPadText = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SSL signed server key and the Certificate Authority's own certificate
|
* The SSL signed server key and the Certificate Authority's own certificate
|
||||||
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
|
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
|
||||||
*/
|
*/
|
||||||
exports.ssl = false;
|
export const ssl = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* socket.io transport methods
|
* socket.io transport methods
|
||||||
**/
|
**/
|
||||||
exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
|
export const socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
|
||||||
|
|
||||||
exports.socketIo = {
|
export const socketIo = {
|
||||||
/**
|
/**
|
||||||
* Maximum permitted client message size (in bytes).
|
* Maximum permitted client message size (in bytes).
|
||||||
*
|
*
|
||||||
|
@ -138,16 +138,16 @@ exports.socketIo = {
|
||||||
/*
|
/*
|
||||||
* The Type of the database
|
* The Type of the database
|
||||||
*/
|
*/
|
||||||
exports.dbType = 'dirty';
|
export const dbType = 'dirty';
|
||||||
/**
|
/**
|
||||||
* This setting is passed with dbType to ueberDB to set up the database
|
* This setting is passed with dbType to ueberDB to set up the database
|
||||||
*/
|
*/
|
||||||
exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
|
export const dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default Text of a new pad
|
* The default Text of a new pad
|
||||||
*/
|
*/
|
||||||
exports.defaultPadText = [
|
export const defaultPadText = [
|
||||||
'Welcome to Etherpad!',
|
'Welcome to Etherpad!',
|
||||||
'',
|
'',
|
||||||
'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' +
|
'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' +
|
||||||
|
@ -159,7 +159,7 @@ exports.defaultPadText = [
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
exports.padOptions = {
|
export const padOptions = {
|
||||||
noColors: false,
|
noColors: false,
|
||||||
showControls: true,
|
showControls: true,
|
||||||
showChat: true,
|
showChat: true,
|
||||||
|
@ -176,7 +176,7 @@ exports.padOptions = {
|
||||||
/**
|
/**
|
||||||
* Whether certain shortcut keys are enabled for a user in the pad
|
* Whether certain shortcut keys are enabled for a user in the pad
|
||||||
*/
|
*/
|
||||||
exports.padShortcutEnabled = {
|
export const padShortcutEnabled = {
|
||||||
altF9: true,
|
altF9: true,
|
||||||
altC: true,
|
altC: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
|
@ -204,7 +204,7 @@ exports.padShortcutEnabled = {
|
||||||
/**
|
/**
|
||||||
* The toolbar buttons and order.
|
* The toolbar buttons and order.
|
||||||
*/
|
*/
|
||||||
exports.toolbar = {
|
export const toolbar = {
|
||||||
left: [
|
left: [
|
||||||
['bold', 'italic', 'underline', 'strikethrough'],
|
['bold', 'italic', 'underline', 'strikethrough'],
|
||||||
['orderedlist', 'unorderedlist', 'indent', 'outdent'],
|
['orderedlist', 'unorderedlist', 'indent', 'outdent'],
|
||||||
|
@ -224,92 +224,92 @@ exports.toolbar = {
|
||||||
/**
|
/**
|
||||||
* A flag that requires any user to have a valid session (via the api) before accessing a pad
|
* A flag that requires any user to have a valid session (via the api) before accessing a pad
|
||||||
*/
|
*/
|
||||||
exports.requireSession = false;
|
export const requireSession = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag that prevents users from creating new pads
|
* A flag that prevents users from creating new pads
|
||||||
*/
|
*/
|
||||||
exports.editOnly = false;
|
export const editOnly = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max age that responses will have (affects caching layer).
|
* Max age that responses will have (affects caching layer).
|
||||||
*/
|
*/
|
||||||
exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours
|
export const maxAge = 1000 * 60 * 60 * 6; // 6 hours
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag that shows if minification is enabled or not
|
* A flag that shows if minification is enabled or not
|
||||||
*/
|
*/
|
||||||
exports.minify = true;
|
export const minify = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path of the abiword executable
|
* The path of the abiword executable
|
||||||
*/
|
*/
|
||||||
exports.abiword = null;
|
export const abiword = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path of the libreoffice executable
|
* The path of the libreoffice executable
|
||||||
*/
|
*/
|
||||||
exports.soffice = null;
|
export const soffice = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path of the tidy executable
|
* The path of the tidy executable
|
||||||
*/
|
*/
|
||||||
exports.tidyHtml = null;
|
export const tidyHtml = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should we support none natively supported file types on import?
|
* Should we support none natively supported file types on import?
|
||||||
*/
|
*/
|
||||||
exports.allowUnknownFileEnds = true;
|
export const allowUnknownFileEnds = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The log level of log4js
|
* The log level of log4js
|
||||||
*/
|
*/
|
||||||
exports.loglevel = defaultLogLevel;
|
export const loglevel = defaultLogLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable IP logging
|
* Disable IP logging
|
||||||
*/
|
*/
|
||||||
exports.disableIPlogging = false;
|
export const disableIPlogging = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of seconds to automatically reconnect pad
|
* Number of seconds to automatically reconnect pad
|
||||||
*/
|
*/
|
||||||
exports.automaticReconnectionTimeout = 0;
|
export const automaticReconnectionTimeout = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable Load Testing
|
* Disable Load Testing
|
||||||
*/
|
*/
|
||||||
exports.loadTest = false;
|
export const loadTest = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable dump of objects preventing a clean exit
|
* Disable dump of objects preventing a clean exit
|
||||||
*/
|
*/
|
||||||
exports.dumpOnUncleanExit = false;
|
export const dumpOnUncleanExit = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable indentation on new lines
|
* Enable indentation on new lines
|
||||||
*/
|
*/
|
||||||
exports.indentationOnNewLine = true;
|
export const indentationOnNewLine = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* log4js appender configuration
|
* log4js appender configuration
|
||||||
*/
|
*/
|
||||||
exports.logconfig = defaultLogConfig();
|
export const logconfig = defaultLogConfig();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Session Key, do not sure this.
|
* Session Key, do not sure this.
|
||||||
*/
|
*/
|
||||||
exports.sessionKey = false;
|
export const sessionKey = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Trust Proxy, whether or not trust the x-forwarded-for header.
|
* Trust Proxy, whether or not trust the x-forwarded-for header.
|
||||||
*/
|
*/
|
||||||
exports.trustProxy = false;
|
export const trustProxy = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Settings controlling the session cookie issued by Etherpad.
|
* Settings controlling the session cookie issued by Etherpad.
|
||||||
*/
|
*/
|
||||||
exports.cookie = {
|
export const cookie = {
|
||||||
/*
|
/*
|
||||||
* Value of the SameSite cookie property. "Lax" is recommended unless
|
* Value of the SameSite cookie property. "Lax" is recommended unless
|
||||||
* Etherpad will be embedded in an iframe from another site, in which case
|
* Etherpad will be embedded in an iframe from another site, in which case
|
||||||
|
@ -331,20 +331,20 @@ exports.cookie = {
|
||||||
* authorization. Note: /admin always requires authentication, and
|
* authorization. Note: /admin always requires authentication, and
|
||||||
* either authorization by a module, or a user with is_admin set
|
* either authorization by a module, or a user with is_admin set
|
||||||
*/
|
*/
|
||||||
exports.requireAuthentication = false;
|
export const requireAuthentication = false;
|
||||||
exports.requireAuthorization = false;
|
export const requireAuthorization = false;
|
||||||
exports.users = {};
|
export const users = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Show settings in admin page, by default it is true
|
* Show settings in admin page, by default it is true
|
||||||
*/
|
*/
|
||||||
exports.showSettingsInAdminPage = true;
|
export const showSettingsInAdminPage = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* By default, when caret is moved out of viewport, it scrolls the minimum
|
* By default, when caret is moved out of viewport, it scrolls the minimum
|
||||||
* height needed to make this line visible.
|
* height needed to make this line visible.
|
||||||
*/
|
*/
|
||||||
exports.scrollWhenFocusLineIsOutOfViewport = {
|
export const scrollWhenFocusLineIsOutOfViewport = {
|
||||||
/*
|
/*
|
||||||
* Percentage of viewport height to be additionally scrolled.
|
* Percentage of viewport height to be additionally scrolled.
|
||||||
*/
|
*/
|
||||||
|
@ -377,12 +377,12 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
|
||||||
*
|
*
|
||||||
* Do not enable on production machines.
|
* Do not enable on production machines.
|
||||||
*/
|
*/
|
||||||
exports.exposeVersion = false;
|
export const exposeVersion = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Override any strings found in locale directories
|
* Override any strings found in locale directories
|
||||||
*/
|
*/
|
||||||
exports.customLocaleStrings = {};
|
export const customLocaleStrings = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
|
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
|
||||||
|
@ -393,12 +393,13 @@ exports.customLocaleStrings = {};
|
||||||
*
|
*
|
||||||
* See https://github.com/nfriedly/express-rate-limit for more options
|
* See https://github.com/nfriedly/express-rate-limit for more options
|
||||||
*/
|
*/
|
||||||
exports.importExportRateLimiting = {
|
export const importExportRateLimiting = {
|
||||||
// duration of the rate limit window (milliseconds)
|
// duration of the rate limit window (milliseconds)
|
||||||
windowMs: 90000,
|
windowMs: 90000,
|
||||||
|
|
||||||
// maximum number of requests per IP to allow during the rate limit window
|
// maximum number of requests per IP to allow during the rate limit window
|
||||||
max: 10,
|
max: 10, onLimitReached: undefined
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -409,7 +410,7 @@ exports.importExportRateLimiting = {
|
||||||
*
|
*
|
||||||
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
|
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
|
||||||
*/
|
*/
|
||||||
exports.commitRateLimiting = {
|
export const commitRateLimiting = {
|
||||||
// duration of the rate limit window (seconds)
|
// duration of the rate limit window (seconds)
|
||||||
duration: 1,
|
duration: 1,
|
||||||
|
|
||||||
|
@ -423,16 +424,16 @@ exports.commitRateLimiting = {
|
||||||
*
|
*
|
||||||
* File size is specified in bytes. Default is 50 MB.
|
* File size is specified in bytes. Default is 50 MB.
|
||||||
*/
|
*/
|
||||||
exports.importMaxFileSize = 50 * 1024 * 1024;
|
export const importMaxFileSize = 50 * 1024 * 1024;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable Admin UI tests
|
* Disable Admin UI tests
|
||||||
*/
|
*/
|
||||||
exports.enableAdminUITests = false;
|
export const enableAdminUITests = false;
|
||||||
|
|
||||||
|
|
||||||
// checks if abiword is avaiable
|
// checks if abiword is avaiable
|
||||||
exports.abiwordAvailable = () => {
|
export const abiwordAvailable = () => {
|
||||||
if (exports.abiword != null) {
|
if (exports.abiword != null) {
|
||||||
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
||||||
} else {
|
} else {
|
||||||
|
@ -440,7 +441,7 @@ exports.abiwordAvailable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sofficeAvailable = () => {
|
export const sofficeAvailable = () => {
|
||||||
if (exports.soffice != null) {
|
if (exports.soffice != null) {
|
||||||
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
||||||
} else {
|
} else {
|
||||||
|
@ -448,9 +449,9 @@ exports.sofficeAvailable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.exportAvailable = () => {
|
export const exportAvailable = () => {
|
||||||
const abiword = exports.abiwordAvailable();
|
const abiword = exports.abiwordAvailable();
|
||||||
const soffice = exports.sofficeAvailable();
|
const soffice = sofficeAvailable();
|
||||||
|
|
||||||
if (abiword === 'no' && soffice === 'no') {
|
if (abiword === 'no' && soffice === 'no') {
|
||||||
return 'no';
|
return 'no';
|
||||||
|
@ -463,7 +464,7 @@ exports.exportAvailable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provide git version if available
|
// Provide git version if available
|
||||||
exports.getGitCommit = () => {
|
export const getGitCommit = () => {
|
||||||
let version = '';
|
let version = '';
|
||||||
try {
|
try {
|
||||||
let rootPath = exports.root;
|
let rootPath = exports.root;
|
||||||
|
@ -482,13 +483,14 @@ exports.getGitCommit = () => {
|
||||||
}
|
}
|
||||||
version = version.substring(0, 7);
|
version = version.substring(0, 7);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Can't get git version for server header\n${e.message}`);
|
const errorCast = e as Error
|
||||||
|
logger.warn(`Can't get git version for server header\n${errorCast.message}`);
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return etherpad version from package.json
|
// Return etherpad version from package.json
|
||||||
exports.getEpVersion = () => require('../../package.json').version;
|
export const getEpVersion = () => require('../../package.json').version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives a settingsObj and, if the property name is a valid configuration
|
* Receives a settingsObj and, if the property name is a valid configuration
|
||||||
|
@ -497,7 +499,8 @@ exports.getEpVersion = () => require('../../package.json').version;
|
||||||
* This code refactors a previous version that copied & pasted the same code for
|
* This code refactors a previous version that copied & pasted the same code for
|
||||||
* both "settings.json" and "credentials.json".
|
* both "settings.json" and "credentials.json".
|
||||||
*/
|
*/
|
||||||
const storeSettings = (settingsObj) => {
|
//FIXME find out what settingsObj is
|
||||||
|
const storeSettings = (settingsObj: any) => {
|
||||||
for (const i of Object.keys(settingsObj || {})) {
|
for (const i of Object.keys(settingsObj || {})) {
|
||||||
if (nonSettings.includes(i)) {
|
if (nonSettings.includes(i)) {
|
||||||
logger.warn(`Ignoring setting: '${i}'`);
|
logger.warn(`Ignoring setting: '${i}'`);
|
||||||
|
@ -536,9 +539,10 @@ const storeSettings = (settingsObj) => {
|
||||||
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
|
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
|
||||||
* in the literal string "null", instead.
|
* in the literal string "null", instead.
|
||||||
*/
|
*/
|
||||||
const coerceValue = (stringValue) => {
|
const coerceValue = (stringValue: string) => {
|
||||||
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
|
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
|
||||||
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
|
const numberToEvaluate = Number(stringValue)
|
||||||
|
const isNumeric = !isNaN(numberToEvaluate) && !isNaN(parseFloat(stringValue)) && isFinite(numberToEvaluate);
|
||||||
|
|
||||||
if (isNumeric) {
|
if (isNumeric) {
|
||||||
// detected numeric string. Coerce to a number
|
// detected numeric string. Coerce to a number
|
||||||
|
@ -591,7 +595,7 @@ const coerceValue = (stringValue) => {
|
||||||
*
|
*
|
||||||
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
|
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
|
||||||
*/
|
*/
|
||||||
const lookupEnvironmentVariables = (obj) => {
|
const lookupEnvironmentVariables = (obj: any) => {
|
||||||
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
|
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
|
||||||
/*
|
/*
|
||||||
* the first invocation of replacer() is with an empty key. Just go on, or
|
* the first invocation of replacer() is with an empty key. Just go on, or
|
||||||
|
@ -652,6 +656,9 @@ const lookupEnvironmentVariables = (obj) => {
|
||||||
`configuration key "${key}". Falling back to default value.`);
|
`configuration key "${key}". Falling back to default value.`);
|
||||||
|
|
||||||
return coerceValue(defaultValue);
|
return coerceValue(defaultValue);
|
||||||
|
} else if ((envVarValue === undefined))
|
||||||
|
{
|
||||||
|
return coerceValue("none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// envVarName contained some value.
|
// envVarName contained some value.
|
||||||
|
@ -666,9 +673,7 @@ const lookupEnvironmentVariables = (obj) => {
|
||||||
return coerceValue(envVarValue);
|
return coerceValue(envVarValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
const newSettings = JSON.parse(stringifiedAndReplaced);
|
return JSON.parse(stringifiedAndReplaced);
|
||||||
|
|
||||||
return newSettings;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -679,7 +684,7 @@ const lookupEnvironmentVariables = (obj) => {
|
||||||
*
|
*
|
||||||
* The isSettings variable only controls the error logging.
|
* The isSettings variable only controls the error logging.
|
||||||
*/
|
*/
|
||||||
const parseSettings = (settingsFilename, isSettings) => {
|
const parseSettings = (settingsFilename: string, isSettings:boolean) => {
|
||||||
let settingsStr = '';
|
let settingsStr = '';
|
||||||
|
|
||||||
let settingsType, notFoundMessage, notFoundFunction;
|
let settingsType, notFoundMessage, notFoundFunction;
|
||||||
|
@ -711,20 +716,22 @@ const parseSettings = (settingsFilename, isSettings) => {
|
||||||
|
|
||||||
logger.info(`${settingsType} loaded from: ${settingsFilename}`);
|
logger.info(`${settingsType} loaded from: ${settingsFilename}`);
|
||||||
|
|
||||||
const replacedSettings = lookupEnvironmentVariables(settings);
|
return lookupEnvironmentVariables(settings);
|
||||||
|
|
||||||
return replacedSettings;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
const error = e as Error
|
||||||
logger.error(`There was an error processing your ${settingsType} ` +
|
logger.error(`There was an error processing your ${settingsType} ` +
|
||||||
`file from ${settingsFilename}: ${e.message}`);
|
`file from ${settingsFilename}: ${error.message}`);
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.reloadSettings = () => {
|
export const randomVersionString = randomString(4);
|
||||||
const settings = parseSettings(exports.settingsFilename, true);
|
|
||||||
const credentials = parseSettings(exports.credentialsFilename, false);
|
|
||||||
|
export const reloadSettings = () => {
|
||||||
|
const settings = parseSettings(settingsFilename, true);
|
||||||
|
const credentials = parseSettings(credentialsFilename, false);
|
||||||
storeSettings(settings);
|
storeSettings(settings);
|
||||||
storeSettings(credentials);
|
storeSettings(credentials);
|
||||||
|
|
||||||
|
@ -772,7 +779,7 @@ exports.reloadSettings = () => {
|
||||||
if (exports.abiword) {
|
if (exports.abiword) {
|
||||||
// Check abiword actually exists
|
// Check abiword actually exists
|
||||||
if (exports.abiword != null) {
|
if (exports.abiword != null) {
|
||||||
fs.exists(exports.abiword, (exists) => {
|
fs.exists(exports.abiword, (exists: boolean) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
|
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
|
||||||
if (!exports.suppressErrorsInPadText) {
|
if (!exports.suppressErrorsInPadText) {
|
||||||
|
@ -786,7 +793,7 @@ exports.reloadSettings = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exports.soffice) {
|
if (exports.soffice) {
|
||||||
fs.exists(exports.soffice, (exists) => {
|
fs.exists(exports.soffice, (exists: boolean) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
const sofficeError =
|
const sofficeError =
|
||||||
'soffice (libreoffice) does not exist at this path, check your settings file.';
|
'soffice (libreoffice) does not exist at this path, check your settings file.';
|
||||||
|
@ -845,7 +852,6 @@ exports.reloadSettings = () => {
|
||||||
* ACHTUNG: this may prevent caching HTTP proxies to work
|
* ACHTUNG: this may prevent caching HTTP proxies to work
|
||||||
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
|
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
|
||||||
*/
|
*/
|
||||||
exports.randomVersionString = randomString(4);
|
|
||||||
logger.info(`Random string used for versioning assets: ${exports.randomVersionString}`);
|
logger.info(`Random string used for versioning assets: ${exports.randomVersionString}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -855,3 +861,5 @@ exports.exportedForTestingOnly = {
|
||||||
|
|
||||||
// initially load settings
|
// initially load settings
|
||||||
exports.reloadSettings();
|
exports.reloadSettings();
|
||||||
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const semver = require('semver');
|
|
||||||
const settings = require('./Settings');
|
|
||||||
const request = require('request');
|
|
||||||
|
|
||||||
let infos;
|
|
||||||
|
|
||||||
const loadEtherpadInformations = () => new Promise((resolve, reject) => {
|
|
||||||
request('https://static.etherpad.org/info.json', (er, response, body) => {
|
|
||||||
if (er) return reject(er);
|
|
||||||
|
|
||||||
try {
|
|
||||||
infos = JSON.parse(body);
|
|
||||||
return resolve(infos);
|
|
||||||
} catch (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.getLatestVersion = () => {
|
|
||||||
exports.needsUpdate();
|
|
||||||
return infos.latestVersion;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.needsUpdate = (cb) => {
|
|
||||||
loadEtherpadInformations().then((info) => {
|
|
||||||
if (semver.gt(info.latestVersion, settings.getEpVersion())) {
|
|
||||||
if (cb) return cb(true);
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(`Can not perform Etherpad update check: ${err}`);
|
|
||||||
if (cb) return cb(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.check = () => {
|
|
||||||
exports.needsUpdate((needsUpdate) => {
|
|
||||||
if (needsUpdate) {
|
|
||||||
console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
59
src/node/utils/UpdateCheck.ts
Normal file
59
src/node/utils/UpdateCheck.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
'use strict';
|
||||||
|
import semver from 'semver';
|
||||||
|
import {getEpVersion} from './Settings';
|
||||||
|
import request from 'request';
|
||||||
|
|
||||||
|
|
||||||
|
type InfoModel = {
|
||||||
|
latestVersion: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let infos: InfoModel|undefined;
|
||||||
|
|
||||||
|
const loadEtherpadInformations = () => new Promise<InfoModel>((resolve, reject) => {
|
||||||
|
request('https://static.etherpad.org/info.json', (er, response, body) => {
|
||||||
|
if (er) reject(er);
|
||||||
|
|
||||||
|
try {
|
||||||
|
infos = JSON.parse(body);
|
||||||
|
if (infos === undefined|| infos === null){
|
||||||
|
reject("Could not retrieve current version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(infos);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLatestVersion = () => {
|
||||||
|
exports.needsUpdate();
|
||||||
|
if(infos === undefined){
|
||||||
|
throw new Error("Could not retrieve latest version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos.latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.needsUpdate = (cb?:(arg0: boolean)=>void) => {
|
||||||
|
loadEtherpadInformations().then((info) => {
|
||||||
|
if (semver.gt(info.latestVersion, getEpVersion())) {
|
||||||
|
if (cb) return cb(true);
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(`Can not perform Etherpad update check: ${err}`);
|
||||||
|
if (cb) return cb(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const check = () => {
|
||||||
|
const needsUpdate = ((needsUpdate: boolean) => {
|
||||||
|
if (needsUpdate && infos) {
|
||||||
|
console.warn(`Update available: Download the latest version ${infos.latestVersion}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
needsUpdate(infos.latestVersion > getEpVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {check, getLatestVersion}
|
|
@ -3,8 +3,6 @@
|
||||||
* Generates a random String with the given length. Is needed to generate the
|
* Generates a random String with the given length. Is needed to generate the
|
||||||
* Author, Group, readonly, session Ids
|
* Author, Group, readonly, session Ids
|
||||||
*/
|
*/
|
||||||
const crypto = require('crypto');
|
import crypto from 'crypto'
|
||||||
|
|
||||||
const randomString = (len) => crypto.randomBytes(len).toString('hex');
|
export const randomString = (len) => crypto.randomBytes(len).toString('hex');
|
||||||
|
|
||||||
module.exports = randomString;
|
|
||||||
|
|
22038
src/package-lock.json
generated
22038
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -49,7 +49,7 @@
|
||||||
"jsonminify": "0.4.2",
|
"jsonminify": "0.4.2",
|
||||||
"languages4translatewiki": "0.1.3",
|
"languages4translatewiki": "0.1.3",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"log4js": "0.6.38",
|
"log4js": "^6.9.1",
|
||||||
"measured-core": "^2.0.0",
|
"measured-core": "^2.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"npm": "^6.14.15",
|
"npm": "^6.14.15",
|
||||||
|
@ -90,7 +90,11 @@
|
||||||
"sinon": "^13.0.2",
|
"sinon": "^13.0.2",
|
||||||
"split-grid": "^1.0.11",
|
"split-grid": "^1.0.11",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5",
|
||||||
|
"@types/node": "^20.3.1",
|
||||||
|
"@types/express": "4.17.17",
|
||||||
|
"concurrently": "^8.2.0",
|
||||||
|
"nodemon": "^2.0.22"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.15.0",
|
"node": ">=14.15.0",
|
||||||
|
@ -103,8 +107,10 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
|
"test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
|
||||||
"test-container": "mocha --timeout 5000 tests/container/specs/api"
|
"test-container": "mocha --timeout 5000 tests/container/specs/api",
|
||||||
|
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/server.js\""
|
||||||
},
|
},
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0",
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,10 @@
|
||||||
* There is one attribute pool per pad, and it includes every current and historical attribute used
|
* There is one attribute pool per pad, and it includes every current and historical attribute used
|
||||||
* in the pad.
|
* in the pad.
|
||||||
*/
|
*/
|
||||||
class AttributePool {
|
export class AttributePool {
|
||||||
|
numToAttrib: {};
|
||||||
|
private attribToNum: {};
|
||||||
|
private nextNum: number;
|
||||||
constructor() {
|
constructor() {
|
||||||
/**
|
/**
|
||||||
* Maps an attribute identifier to the attribute's `[key, value]` string pair.
|
* Maps an attribute identifier to the attribute's `[key, value]` string pair.
|
11
src/tsconfig.json
Normal file
11
src/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "es6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"lib": ["es2015"]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue