From 3c2129b1ccd14817f28b39b2d5c30a1297aa66b2 Mon Sep 17 00:00:00 2001
From: SamTV12345 <40429738+samtv12345@users.noreply.github.com>
Date: Fri, 23 Jun 2023 18:57:36 +0200
Subject: [PATCH] Rewrote server in typescript.
---
src/.gitignore | 3 +-
src/node/db/API.ts | 16 +-
src/node/db/AuthorManager.ts | 4 +-
src/node/db/GroupManager.ts | 2 +-
src/node/db/Pad.ts | 55 ++++---
src/node/db/PadManager.ts | 2 +-
src/node/db/ReadOnlyManager.ts | 2 +-
src/node/db/SecurityManager.ts | 4 +-
src/node/handler/APIHandler.ts | 6 +-
src/node/handler/ImportHandler.ts | 8 +-
src/node/handler/PadMessageHandler.ts | 18 +--
src/node/handler/SocketIORouter.ts | 8 +-
src/node/hooks/{express.js => express.ts} | 142 +++++++++++-------
src/node/hooks/express/importexport.ts | 4 +-
.../{padurlsanitize.js => padurlsanitize.ts} | 6 +-
.../express/{socketio.js => socketio.ts} | 30 ++--
.../{specialpages.js => specialpages.ts} | 42 +++---
.../hooks/express/{static.js => static.ts} | 12 +-
src/node/hooks/express/{tests.js => tests.ts} | 22 +--
.../express/{webaccess.js => webaccess.ts} | 60 ++++----
src/node/hooks/{i18n.js => i18n.ts} | 36 ++---
src/node/models/CMDOptions.ts | 11 ++
src/node/models/LogLevel.ts | 1 +
src/node/models/PadDiffModel.ts | 3 +
src/node/models/Presession.ts | 5 +
src/node/models/SessionSocketModel.ts | 2 +
src/node/models/UserIndexedModel.ts | 5 +
src/node/server.ts | 2 +
src/node/utils/{Abiword.js => Abiword.ts} | 25 +--
.../{AbsolutePaths.js => AbsolutePaths.ts} | 15 +-
src/node/utils/{Cli.js => Cli.ts} | 3 +-
.../{ExportEtherpad.js => ExportEtherpad.ts} | 19 ++-
.../{ExportHelper.js => ExportHelper.ts} | 19 ++-
.../utils/{ExportHtml.js => ExportHtml.ts} | 36 ++---
src/node/utils/{ExportTxt.js => ExportTxt.ts} | 7 +-
.../{ImportEtherpad.js => ImportEtherpad.ts} | 26 ++--
.../utils/{ImportHtml.js => ImportHtml.ts} | 12 +-
.../utils/{LibreOffice.js => LibreOffice.ts} | 23 +--
src/node/utils/{Minify.js => Minify.ts} | 43 +++---
.../{MinifyWorker.js => MinifyWorker.ts} | 13 +-
src/node/utils/Settings.ts | 121 ++++++++-------
src/node/utils/{Stream.js => Stream.ts} | 6 +-
src/node/utils/{TidyHtml.js => TidyHtml.ts} | 11 +-
...ng_middleware.js => caching_middleware.ts} | 42 ++++--
.../utils/{customError.js => customError.ts} | 6 +-
src/node/utils/{padDiff.js => padDiff.ts} | 31 ++--
.../utils/{path_exists.js => path_exists.ts} | 8 +-
src/node/utils/{promises.js => promises.ts} | 10 +-
.../{randomstring.js => randomstring.ts} | 0
src/node/utils/{run_cmd.js => run_cmd.ts} | 53 ++++---
...anitizePathname.js => sanitizePathname.ts} | 6 +-
src/node/utils/{toolbar.js => toolbar.ts} | 6 +-
src/package-lock.json | 23 +++
src/package.json | 12 +-
.../js/{ace2_common.js => ace2_common.ts} | 19 +--
src/static/js/{pad_utils.js => pad_utils.ts} | 50 +++---
src/tsconfig.json | 2 +
57 files changed, 668 insertions(+), 490 deletions(-)
rename src/node/hooks/{express.js => express.ts} (71%)
rename src/node/hooks/express/{padurlsanitize.js => padurlsanitize.ts} (83%)
rename src/node/hooks/express/{socketio.js => socketio.ts} (88%)
rename src/node/hooks/express/{specialpages.js => specialpages.ts} (63%)
rename src/node/hooks/express/{static.js => static.ts} (86%)
rename src/node/hooks/express/{tests.js => tests.ts} (81%)
rename src/node/hooks/express/{webaccess.js => webaccess.ts} (85%)
rename src/node/hooks/{i18n.js => i18n.ts} (79%)
create mode 100644 src/node/models/CMDOptions.ts
create mode 100644 src/node/models/LogLevel.ts
create mode 100644 src/node/models/PadDiffModel.ts
create mode 100644 src/node/models/Presession.ts
create mode 100644 src/node/models/UserIndexedModel.ts
rename src/node/utils/{Abiword.js => Abiword.ts} (80%)
rename src/node/utils/{AbsolutePaths.js => AbsolutePaths.ts} (94%)
rename src/node/utils/{Cli.js => Cli.ts} (96%)
rename src/node/utils/{ExportEtherpad.js => ExportEtherpad.ts} (84%)
rename src/node/utils/{ExportHelper.js => ExportHelper.ts} (85%)
rename src/node/utils/{ExportHtml.js => ExportHtml.ts} (95%)
rename src/node/utils/{ExportTxt.js => ExportTxt.ts} (97%)
rename src/node/utils/{ImportEtherpad.js => ImportEtherpad.ts} (86%)
rename src/node/utils/{ImportHtml.js => ImportHtml.ts} (92%)
rename src/node/utils/{LibreOffice.js => LibreOffice.ts} (91%)
rename src/node/utils/{Minify.js => Minify.ts} (91%)
rename src/node/utils/{MinifyWorker.js => MinifyWorker.ts} (80%)
rename src/node/utils/{Stream.js => Stream.ts} (98%)
rename src/node/utils/{TidyHtml.js => TidyHtml.ts} (83%)
rename src/node/utils/{caching_middleware.js => caching_middleware.ts} (90%)
rename src/node/utils/{customError.js => customError.ts} (87%)
rename src/node/utils/{padDiff.js => padDiff.ts} (94%)
rename src/node/utils/{path_exists.js => path_exists.ts} (71%)
rename src/node/utils/{promises.js => promises.ts} (93%)
rename src/node/utils/{randomstring.js => randomstring.ts} (100%)
rename src/node/utils/{run_cmd.js => run_cmd.ts} (81%)
rename src/node/utils/{sanitizePathname.js => sanitizePathname.ts} (94%)
rename src/node/utils/{toolbar.js => toolbar.ts} (98%)
rename src/static/js/{ace2_common.js => ace2_common.ts} (78%)
rename src/static/js/{pad_utils.js => pad_utils.ts} (92%)
diff --git a/src/.gitignore b/src/.gitignore
index 1521c8b76..81cd8b481 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -1 +1,2 @@
-dist
+dist/
+node_modules
diff --git a/src/node/db/API.ts b/src/node/db/API.ts
index 99b19498f..d434eb556 100644
--- a/src/node/db/API.ts
+++ b/src/node/db/API.ts
@@ -21,7 +21,7 @@
import Changeset from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage';
-import CustomError from '../utils/customError';
+import {CustomError} from '../utils/customError';
import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager';
import {
handleCustomMessage,
@@ -40,11 +40,11 @@ import {
} 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';
+import {getTXTFromAtext} from '../utils/ExportTxt';
+import {setPadHTML} from '../utils/ImportHtml';
const cleanText = require('./Pad').cleanText;
-import PadDiff from '../utils/padDiff';
+import {PadDiff} from '../utils/padDiff';
+import {getPadHTMLDocument} from "../utils/ExportHtml";
/* ********************
* GROUP FUNCTIONS ****
@@ -192,7 +192,7 @@ export const getText = async (padID, rev) => {
}
// the client wants the latest text, lets return it to him
- const text = exportTxt.getTXTFromAtext(pad, pad.atext);
+ const text = getTXTFromAtext(pad, pad.atext);
return {text};
};
@@ -263,7 +263,7 @@ export const getHTML = async (padID, rev) => {
}
// get the html of this revision
- let html = await exportHtml.getPadHTML(pad, rev);
+ let html = await getPadHTMLDocument(pad, rev);
// wrap the HTML
html = `
${html}`;
@@ -289,7 +289,7 @@ export const setHTML = async (padID, html, authorId = '') => {
// add a new changeset with the new html to the pad
try {
- await importHtml.setPadHTML(pad, cleanText(html), authorId);
+ await setPadHTML(pad, cleanText(html), authorId);
} catch (e) {
throw new CustomError('HTML is malformed', 'apierror');
}
diff --git a/src/node/db/AuthorManager.ts b/src/node/db/AuthorManager.ts
index fd825bba6..a3f5db0dc 100644
--- a/src/node/db/AuthorManager.ts
+++ b/src/node/db/AuthorManager.ts
@@ -20,7 +20,7 @@
*/
import {db} from './DB';
-import CustomError from '../utils/customError';
+import {CustomError} from '../utils/customError';
import hooks from '../../static/js/pluginfw/hooks.js';
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
@@ -283,7 +283,7 @@ export const addPad = async (authorID: string, padID: string) => {
* @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
-export const removePad = async (authorID: string, padID: string) => {
+export const removePad = async (authorID: string, padID?: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
if (author == null) return;
diff --git a/src/node/db/GroupManager.ts b/src/node/db/GroupManager.ts
index 6d4e2d0e1..0be2205d8 100644
--- a/src/node/db/GroupManager.ts
+++ b/src/node/db/GroupManager.ts
@@ -19,7 +19,7 @@
* limitations under the License.
*/
-import CustomError from '../utils/customError';
+import {CustomError} from '../utils/customError';
const randomString = require('../../static/js/pad_utils').randomString;
import {db} from './DB';
import {doesPadExist, getPad} from './PadManager';
diff --git a/src/node/db/Pad.ts b/src/node/db/Pad.ts
index 9f1742705..be7ac0155 100644
--- a/src/node/db/Pad.ts
+++ b/src/node/db/Pad.ts
@@ -7,22 +7,21 @@ import AttributeMap from '../../static/js/AttributeMap';
import Changeset from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage';
import {AttributePool} from '../../static/js/AttributePool';
-import Stream from '../utils/Stream';
+import {Stream} from '../utils/Stream';
import assert, {strict} from 'assert'
import {db} from './DB';
import {defaultPadText} from '../utils/Settings';
import {addPad, getAuthorColorId, getAuthorName, getColorPalette, removePad} from './AuthorManager';
import {Revision} from "../models/Revision";
-const padManager = require('./PadManager');
-const padMessageHandler = require('../handler/PadMessageHandler');
-const groupManager = require('./GroupManager');
-const CustomError = require('../utils/customError');
-const readOnlyManager = require('./ReadOnlyManager');
-const randomString = require('../utils/randomstring');
-const hooks = require('../../static/js/pluginfw/hooks');
-const {padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
-const promises = require('../utils/promises');
-
+import {doesPadExist, getPad} from './PadManager';
+import {kickSessionsFromPad} from '../handler/PadMessageHandler';
+import {doesGroupExist} from './GroupManager';
+import {CustomError} from '../utils/customError';
+import {getReadOnlyId} from './ReadOnlyManager';
+import {randomString} from '../utils/randomstring';
+import hooks from '../../static/js/pluginfw/hooks';
+import {timesLimit} from '../utils/promises';
+import {padutils} from '../../static/js/pad_utils';
/**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
* line breaks and convert Tabs to spaces
@@ -114,11 +113,11 @@ export class Pad {
pad: this,
authorId,
get author() {
- warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
+ padutils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
return this.authorId;
},
set author(authorId) {
- warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
+ padutils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
this.authorId = authorId;
},
...this.head === 0 ? {} : {
@@ -403,16 +402,16 @@ export class Pad {
for (const p of new Stream(promises).batch(100).buffer(99)) await p;
// Initialize the new pad (will update the listAllPads cache)
- const dstPad = await padManager.getPad(destinationID, null);
+ const dstPad = await getPad(destinationID, null);
// let the plugins know the pad was copied
await hooks.aCallAll('padCopy', {
get originalPad() {
- warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
+ padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
return this.srcPad;
},
get destinationID() {
- warnDeprecated(
+ padutils.warnDeprecated(
'padCopy destinationID context property is deprecated; use dstPad.id instead');
return this.dstPad.id;
},
@@ -428,7 +427,7 @@ export class Pad {
if (destinationID.indexOf('$') >= 0) {
destGroupID = destinationID.split('$')[0];
- const groupExists = await groupManager.doesGroupExist(destGroupID);
+ const groupExists = await doesGroupExist(destGroupID);
// group does not exist
if (!groupExists) {
@@ -440,7 +439,7 @@ export class Pad {
async removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
// if the pad exists, we should abort, unless forced.
- const exists = await padManager.doesPadExist(destinationID);
+ const exists = await doesPadExist(destinationID);
// allow force to be a string
if (typeof force === 'string') {
@@ -456,7 +455,7 @@ export class Pad {
}
// exists and forcing
- const pad = await padManager.getPad(destinationID);
+ const pad = await getPad(destinationID);
await pad.remove();
}
}
@@ -485,7 +484,7 @@ export class Pad {
}
// initialize the pad with a new line to avoid getting the defaultText
- const dstPad = await padManager.getPad(destinationID, '\n', authorId);
+ const dstPad = await getPad(destinationID, '\n', authorId);
dstPad.pool = this.pool.clone();
const oldAText = this.atext;
@@ -509,11 +508,11 @@ export class Pad {
await hooks.aCallAll('padCopy', {
get originalPad() {
- warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
+ padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
return this.srcPad;
},
get destinationID() {
- warnDeprecated(
+ padutils.warnDeprecated(
'padCopy destinationID context property is deprecated; use dstPad.id instead');
return this.dstPad.id;
},
@@ -529,7 +528,7 @@ export class Pad {
const p = [];
// kick everyone from this pad
- padMessageHandler.kickSessionsFromPad(padID);
+ kickSessionsFromPad(padID);
// delete all relations - the original code used async.parallel but
// none of the operations except getting the group depended on callbacks
@@ -550,18 +549,18 @@ export class Pad {
}
// remove the readonly entries
- p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
+ p.push(getReadOnlyId(padID).then(async (readonlyID) => {
await db.remove(`readonly2pad:${readonlyID}`);
}));
p.push(db.remove(`pad2readonly:${padID}`));
// delete all chat messages
- p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
+ p.push(timesLimit(this.chatHead + 1, 500, async (i) => {
await this.db.remove(`pad:${this.id}:chat:${i}`, null);
}));
// delete all revisions
- p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
+ p.push(timesLimit(this.head + 1, 500, async (i) => {
await this.db.remove(`pad:${this.id}:revs:${i}`, null);
}));
@@ -571,10 +570,10 @@ export class Pad {
});
// delete the pad entry and delete pad from padManager
- p.push(padManager.removePad(padID));
+ p.push(removePad(padID));
p.push(hooks.aCallAll('padRemove', {
get padID() {
- warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
+ padutils.warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
return this.pad.id;
},
pad: this,
diff --git a/src/node/db/PadManager.ts b/src/node/db/PadManager.ts
index 568c19f83..a48473546 100644
--- a/src/node/db/PadManager.ts
+++ b/src/node/db/PadManager.ts
@@ -19,7 +19,7 @@
* limitations under the License.
*/
-import CustomError from '../utils/customError';
+import {CustomError} from '../utils/customError';
import {Pad} from './Pad';
import {db} from './DB';
diff --git a/src/node/db/ReadOnlyManager.ts b/src/node/db/ReadOnlyManager.ts
index fe763cd9a..8d5a036c8 100644
--- a/src/node/db/ReadOnlyManager.ts
+++ b/src/node/db/ReadOnlyManager.ts
@@ -21,7 +21,7 @@
import {db} from './DB';
-import randomString from '../utils/randomstring';
+import {randomString} from '../utils/randomstring';
/**
diff --git a/src/node/db/SecurityManager.ts b/src/node/db/SecurityManager.ts
index dcebdb897..ca894b5e5 100644
--- a/src/node/db/SecurityManager.ts
+++ b/src/node/db/SecurityManager.ts
@@ -31,7 +31,7 @@ import {findAuthorID} from "./SessionManager";
import {editOnly, loadTest, requireAuthentication, requireSession} from "../utils/Settings";
-import webaccess from "../hooks/express/webaccess";
+import {normalizeAuthzLevel} from "../hooks/express/webaccess";
import log4js from "log4js";
@@ -92,7 +92,7 @@ export const checkAccess = async (padID, sessionCookie, token, userSettings) =>
// Note: userSettings.padAuthorizations should still be populated even if
// settings.requireAuthorization is false.
const padAuthzs = userSettings.padAuthorizations || {};
- const level = webaccess.normalizeAuthzLevel(padAuthzs[padID]);
+ const level = normalizeAuthzLevel(padAuthzs[padID]);
if (!level) {
authLogger.debug('access denied: unauthorized');
return DENY;
diff --git a/src/node/handler/APIHandler.ts b/src/node/handler/APIHandler.ts
index d1ed23fb3..5b485f33a 100644
--- a/src/node/handler/APIHandler.ts
+++ b/src/node/handler/APIHandler.ts
@@ -19,12 +19,12 @@
* limitations under the License.
*/
-import absolutePaths from '../utils/AbsolutePaths';
+import {makeAbsolute} from '../utils/AbsolutePaths';
import fs from 'fs';
import * as api from '../db/API';
import log4js from 'log4js';
import {sanitizePadId} from '../db/PadManager';
-import randomString from '../utils/randomstring';
+import {randomString} from '../utils/randomstring';
const argv = require('../utils/Cli').argv;
const createHTTPError = require('http-errors');
@@ -32,7 +32,7 @@ const apiHandlerLogger = log4js.getLogger('APIHandler');
// ensure we have an apikey
let apikey = null;
-const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
+const apikeyFilename = makeAbsolute(argv.apikey || './APIKEY.txt');
try {
apikey = fs.readFileSync(apikeyFilename, 'utf8');
diff --git a/src/node/handler/ImportHandler.ts b/src/node/handler/ImportHandler.ts
index 889422564..92f5e38a7 100644
--- a/src/node/handler/ImportHandler.ts
+++ b/src/node/handler/ImportHandler.ts
@@ -29,8 +29,8 @@ import {promises as fs} from "fs";
import {abiword, allowUnknownFileEnds, importMaxFileSize, soffice} from '../utils/Settings';
import {Formidable} from 'formidable';
import os from 'os';
-import importHtml from '../utils/ImportHtml';
-import importEtherpad from '../utils/ImportEtherpad';
+import {setPadHTML} from '../utils/ImportHtml';
+import {setPadRaw} from '../utils/ImportEtherpad';
import log4js from 'log4js';
import hooks from '../../static/js/pluginfw/hooks.js';
@@ -150,7 +150,7 @@ const doImport = async (req, res, padId, authorId) => {
}
const text = await fs.readFile(srcFile, 'utf8');
directDatabaseAccess = true;
- await importEtherpad.setPadRaw(padId, text, authorId);
+ await setPadRaw(padId, text, authorId);
}
// convert file to html if necessary
@@ -207,7 +207,7 @@ const doImport = async (req, res, padId, authorId) => {
if (!directDatabaseAccess) {
if (importHandledByPlugin || useConverter || fileIsHTML) {
try {
- await importHtml.setPadHTML(pad, text, authorId);
+ await setPadHTML(pad, text, authorId);
} catch (err) {
logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`);
}
diff --git a/src/node/handler/PadMessageHandler.ts b/src/node/handler/PadMessageHandler.ts
index dfa80430d..5e9f0665d 100644
--- a/src/node/handler/PadMessageHandler.ts
+++ b/src/node/handler/PadMessageHandler.ts
@@ -58,7 +58,7 @@ import {createCollection} from '../stats';
import {strict as assert} from "assert";
import {RateLimiterMemory} from 'rate-limiter-flexible';
-import webaccess from '../hooks/express/webaccess';
+import {userCanModify} from '../hooks/express/webaccess';
import {ErrorCaused} from "../models/ErrorCaused";
import {Pad} from "../db/Pad";
import {SessionInfo} from "../models/SessionInfo";
@@ -164,7 +164,7 @@ const padChannels = new Channels((ch, {socket, message}) => handleUserChanges(so
* This Method is called by server.js to tell the message handler on which socket it should send
* @param socket_io The Socket
*/
-exports.setSocketIO = (socket_io) => {
+export const setSocketIO = (socket_io) => {
socketio = socket_io;
};
@@ -172,7 +172,7 @@ exports.setSocketIO = (socket_io) => {
* Handles the connection of a new user
* @param socket the socket.io Socket object for the new connection from the client
*/
-exports.handleConnect = (socket) => {
+export const handleConnect = (socket) => {
createCollection.meter('connects').mark();
// Initialize sessioninfos for this new session
@@ -186,7 +186,7 @@ exports.handleConnect = (socket) => {
/**
* Kicks all sessions from a pad
*/
-exports.kickSessionsFromPad = (padID) => {
+export const kickSessionsFromPad = (padID) => {
if (typeof socketio.sockets.clients !== 'function') return;
// skip if there is nobody on this pad
@@ -200,7 +200,7 @@ exports.kickSessionsFromPad = (padID) => {
* Handles the disconnection of a user
* @param socket the socket.io Socket object for the client
*/
-exports.handleDisconnect = async (socket) => {
+export const handleDisconnect = async (socket) => {
createCollection.meter('disconnects').mark();
const session = sessioninfos[socket.id];
delete sessioninfos[socket.id];
@@ -238,7 +238,7 @@ exports.handleDisconnect = async (socket) => {
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
-exports.handleMessage = async (socket, message) => {
+export const handleMessage = async (socket, message) => {
const env = process.env.NODE_ENV || 'development';
if (env === 'production') {
@@ -272,7 +272,7 @@ exports.handleMessage = async (socket, message) => {
thisSession.padId = padIds.padId;
thisSession.readOnlyPadId = padIds.readOnlyPadId;
thisSession.readonly =
- padIds.readonly || !webaccess.userCanModify(thisSession.auth.padID, socket.client.request);
+ padIds.readonly || !userCanModify(thisSession.auth.padID, socket.client.request);
}
// Outside of the checks done by this function, message.padId must not be accessed because it is
// too easy to introduce a security vulnerability that allows malicious users to read or modify
@@ -416,7 +416,7 @@ const handleSaveRevisionMessage = async (socket, message) => {
* @param msg {Object} the message we're sending
* @param sessionID {string} the socketIO session to which we're sending this message
*/
-exports.handleCustomObjectMessage = (msg, sessionID) => {
+export const handleCustomObjectMessage = (msg, sessionID) => {
if (msg.data.type === 'CUSTOM') {
if (sessionID) {
// a sessionID is targeted: directly to this sessionID
@@ -684,7 +684,7 @@ const handleUserChanges = async (socket, message) => {
socket.json.send({type: 'COLLABROOM', data: {type: 'ACCEPT_COMMIT', newRev}});
thisSession.rev = newRev;
if (newRev !== r) thisSession.time = await pad.getRevisionDate(newRev);
- await exports.updatePadClients(pad);
+ await updatePadClients(pad);
} catch (err) {
socket.json.send({disconnect: 'badChangeset'});
createCollection.meter('failedChangesets').mark();
diff --git a/src/node/handler/SocketIORouter.ts b/src/node/handler/SocketIORouter.ts
index 73594bfb0..9f7875566 100644
--- a/src/node/handler/SocketIORouter.ts
+++ b/src/node/handler/SocketIORouter.ts
@@ -39,18 +39,18 @@ let io;
/**
* adds a component
*/
-exports.addComponent = (moduleName, module) => {
- if (module == null) return exports.deleteComponent(moduleName);
+export const addComponent = (moduleName, module) => {
+ if (module == null) return deleteComponent(moduleName);
components[moduleName] = module;
module.setSocketIO(io);
};
-exports.deleteComponent = (moduleName) => { delete components[moduleName]; };
+export const deleteComponent = (moduleName) => { delete components[moduleName]; };
/**
* sets the socket.io and adds event functions for routing
*/
-exports.setSocketIO = (_io) => {
+export const setSocketIO = (_io) => {
io = _io;
io.sockets.on('connection', (socket) => {
diff --git a/src/node/hooks/express.js b/src/node/hooks/express.ts
similarity index 71%
rename from src/node/hooks/express.js
rename to src/node/hooks/express.ts
index 9c42fd6d8..74f5834d2 100644
--- a/src/node/hooks/express.js
+++ b/src/node/hooks/express.ts
@@ -1,34 +1,57 @@
'use strict';
-const _ = require('underscore');
-const cookieParser = require('cookie-parser');
-const events = require('events');
-const express = require('express');
-const expressSession = require('express-session');
-const fs = require('fs');
-const hooks = require('../../static/js/pluginfw/hooks');
-const log4js = require('log4js');
-const SessionStore = require('../db/SessionStore');
-const settings = require('../utils/Settings');
-const stats = require('../stats');
-const util = require('util');
-const webaccess = require('./express/webaccess');
+import _ from 'underscore';
+import cookieParser from 'cookie-parser';
+import events from "events";
+
+import express from "express";
+
+import fs from "fs";
+
+import expressSession from "express-session";
+
+import hooks from "../../static/js/pluginfw/hooks";
+
+import log4js from "log4js";
+
+import SessionStore from "../db/SessionStore";
+
+import {
+ cookie,
+ exposeVersion,
+ getEpVersion,
+ getGitCommit,
+ ip,
+ loglevel,
+ port,
+ sessionKey,
+ ssl, sslKeys,
+ trustProxy,
+ users
+} from "../utils/Settings";
+
+import {createCollection} from "../stats";
+
+import util from "util";
+
+import {checkAccess, checkAccess2} from "./express/webaccess";
+import {Socket} from "net";
const logger = log4js.getLogger('http');
let serverName;
let sessionStore;
-const sockets = new Set();
+const sockets = new Set();
const socketsEvents = new events.EventEmitter();
-const startTime = stats.settableGauge('httpStartTime');
-
-exports.server = null;
+const startTime = createCollection.settableGauge('httpStartTime');
+export let server = null;
+export let sessionMiddleware;
const closeServer = async () => {
- if (exports.server != null) {
+ if (server != null) {
logger.info('Closing HTTP server...');
// Call exports.server.close() to reject new connections but don't await just yet because the
// Promise won't resolve until all preexisting connections are closed.
- const p = util.promisify(exports.server.close.bind(exports.server))();
+ const p = util.promisify(server.close.bind(server))();
await hooks.aCallAll('expressCloseServer');
// Give existing connections some time to close on their own before forcibly terminating. The
// time should be long enough to avoid interrupting most preexisting transmissions but short
@@ -47,7 +70,7 @@ const closeServer = async () => {
}
await p;
clearTimeout(timeout);
- exports.server = null;
+ server = null;
startTime.setValue(0);
logger.info('HTTP server closed');
}
@@ -55,24 +78,24 @@ const closeServer = async () => {
sessionStore = null;
};
-exports.createServer = async () => {
+export const createServer = async () => {
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
- serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
+ serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`;
- console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
+ console.log(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`);
- await exports.restartServer();
+ await restartServer();
- if (settings.ip === '') {
+ if (ip.length===0) {
// using Unix socket for connectivity
- console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
+ console.log(`You can access your Etherpad instance using the Unix socket at ${port}`);
} else {
- console.log(`You can access your Etherpad instance at http://${settings.ip}:${settings.port}/`);
+ console.log(`You can access your Etherpad instance at http://${ip}:${port}/`);
}
- if (!_.isEmpty(settings.users)) {
- console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`);
+ if (!_.isEmpty(users)) {
+ console.log(`The plugin admin page is at http://${ip}:${port}/admin/plugins`);
} else {
console.warn('Admin username and password not set in settings.json. ' +
'To access admin please uncomment and edit "users" in settings.json');
@@ -87,39 +110,40 @@ exports.createServer = async () => {
}
};
-exports.restartServer = async () => {
+export const restartServer = async () => {
await closeServer();
const app = express(); // New syntax for express v3
- if (settings.ssl) {
+ if (ssl) {
console.log('SSL -- enabled');
- console.log(`SSL -- server key file: ${settings.ssl.key}`);
- console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
+ console.log(`SSL -- server key file: ${sslKeys.key}`);
+ console.log(`SSL -- Certificate Authority's certificate file: ${sslKeys.cert}`);
const options = {
- key: fs.readFileSync(settings.ssl.key),
- cert: fs.readFileSync(settings.ssl.cert),
+ key: fs.readFileSync(sslKeys.key),
+ cert: fs.readFileSync(sslKeys.cert),
+ ca: undefined
};
- if (settings.ssl.ca) {
+ if (sslKeys.ca) {
options.ca = [];
- for (let i = 0; i < settings.ssl.ca.length; i++) {
- const caFileName = settings.ssl.ca[i];
+ for (let i = 0; i < sslKeys.ca.length; i++) {
+ const caFileName = sslKeys.ca[i];
options.ca.push(fs.readFileSync(caFileName));
}
}
const https = require('https');
- exports.server = https.createServer(options, app);
+ server = https.createServer(options, app);
} else {
const http = require('http');
- exports.server = http.createServer(app);
+ server = http.createServer(app);
}
app.use((req, res, next) => {
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
- if (settings.ssl) {
+ if (ssl) {
// we use SSL
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
@@ -138,14 +162,14 @@ exports.restartServer = async () => {
res.header('Referrer-Policy', 'same-origin');
// send git version in the Server response header if exposeVersion is true.
- if (settings.exposeVersion) {
+ if (exposeVersion) {
res.header('Server', serverName);
}
next();
});
- if (settings.trustProxy) {
+ if (trustProxy) {
/*
* If 'trust proxy' === true, the client’s IP address in req.ip will be the
* left-most entry in the X-Forwarded-* header.
@@ -157,8 +181,10 @@ exports.restartServer = async () => {
// Measure response time
app.use((req, res, next) => {
- const stopWatch = stats.timer('httpRequests').start();
+ const stopWatch = createCollection.timer('httpRequests').start();
const sendFn = res.send.bind(res);
+ // FIXME Check if this is still needed
+ // @ts-ignore
res.send = (...args) => { stopWatch.end(); sendFn(...args); };
next();
});
@@ -167,20 +193,20 @@ exports.restartServer = async () => {
// starts listening to requests as reported in issue #158. Not installing the log4js connect
// logger when the log level has a higher severity than INFO since it would not log at that level
// anyway.
- if (!(settings.loglevel === 'WARN' && settings.loglevel === 'ERROR')) {
+ if (!(loglevel === 'WARN') && loglevel === 'ERROR') {
app.use(log4js.connectLogger(logger, {
- level: log4js.levels.DEBUG,
+ level: loglevel,
format: ':status, :method :url',
}));
}
- app.use(cookieParser(settings.sessionKey, {}));
+ app.use(cookieParser(sessionKey, {}));
- sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
- exports.sessionMiddleware = expressSession({
+ sessionStore = new SessionStore(cookie.sessionRefreshInterval);
+sessionMiddleware = expressSession({
propagateTouch: true,
rolling: true,
- secret: settings.sessionKey,
+ secret: sessionKey,
store: sessionStore,
resave: false,
saveUninitialized: false,
@@ -188,8 +214,8 @@ exports.restartServer = async () => {
// cleaner :)
name: 'express_sid',
cookie: {
- maxAge: settings.cookie.sessionLifetime || null, // Convert 0 to null.
- sameSite: settings.cookie.sameSite,
+ maxAge: cookie.sessionLifetime || null, // Convert 0 to null.
+ sameSite: cookie.sameSite,
// The automatic express-session mechanism for determining if the application is being served
// over ssl is similar to the one used for setting the language cookie, which check if one of
@@ -215,15 +241,15 @@ exports.restartServer = async () => {
// middleware. This allows plugins to avoid creating an express-session record in the database
// when it is not needed (e.g., public static content).
await hooks.aCallAll('expressPreSession', {app});
- app.use(exports.sessionMiddleware);
+ app.use(sessionMiddleware);
- app.use(webaccess.checkAccess);
+ app.use(checkAccess2);
await Promise.all([
hooks.aCallAll('expressConfigure', {app}),
- hooks.aCallAll('expressCreateServer', {app, server: exports.server}),
+ hooks.aCallAll('expressCreateServer', {app, server: server}),
]);
- exports.server.on('connection', (socket) => {
+ server.on('connection', (socket) => {
sockets.add(socket);
socketsEvents.emit('updated');
socket.on('close', () => {
@@ -231,11 +257,11 @@ exports.restartServer = async () => {
socketsEvents.emit('updated');
});
});
- await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
+ await util.promisify(server.listen).bind(server)(port, ip);
startTime.setValue(Date.now());
logger.info('HTTP server listening for connections');
};
-exports.shutdown = async (hookName, context) => {
+export const shutdown = async (hookName, context) => {
await closeServer();
};
diff --git a/src/node/hooks/express/importexport.ts b/src/node/hooks/express/importexport.ts
index 5df195599..e5d096b76 100644
--- a/src/node/hooks/express/importexport.ts
+++ b/src/node/hooks/express/importexport.ts
@@ -8,7 +8,7 @@ import {doesPadExist} from '../../db/PadManager';
import {getPadId, isReadOnlyId} from '../../db/ReadOnlyManager';
import rateLimit from 'express-rate-limit';
import {checkAccess} from '../../db/SecurityManager';
-import webaccess from './webaccess';
+import {userCanModify} from './webaccess';
exports.expressCreateServer = (hookName, args, cb) => {
importExportRateLimiting.onLimitReached = (req, res, options) => {
@@ -72,7 +72,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
const {session: {user} = {}}:SessionSocketModel = req;
const {accessStatus, authorID: authorId} = await checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
- if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) {
+ if (accessStatus !== 'grant' || !userCanModify(req.params.pad, req)) {
return res.status(403).send('Forbidden');
}
await doImport2(req, res, req.params.pad, authorId);
diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.ts
similarity index 83%
rename from src/node/hooks/express/padurlsanitize.js
rename to src/node/hooks/express/padurlsanitize.ts
index ff1afa477..00006bca4 100644
--- a/src/node/hooks/express/padurlsanitize.js
+++ b/src/node/hooks/express/padurlsanitize.ts
@@ -1,18 +1,18 @@
'use strict';
-const padManager = require('../../db/PadManager');
+import {isValidPadId, sanitizePadId} from '../../db/PadManager';
exports.expressCreateServer = (hookName, args, cb) => {
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', (req, res, next, padId) => {
(async () => {
// ensure the padname is valid and the url doesn't end with a /
- if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
+ if (!isValidPadId(padId) || /\/$/.test(req.url)) {
res.status(404).send('Such a padname is forbidden');
return;
}
- const sanitizedPadId = await padManager.sanitizePadId(padId);
+ const sanitizedPadId = await sanitizePadId(padId);
if (sanitizedPadId === padId) {
// the pad id was fine, so just render it
diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.ts
similarity index 88%
rename from src/node/hooks/express/socketio.js
rename to src/node/hooks/express/socketio.ts
index edb679940..feed1a2fb 100644
--- a/src/node/hooks/express/socketio.js
+++ b/src/node/hooks/express/socketio.ts
@@ -1,14 +1,14 @@
'use strict';
-const events = require('events');
-const express = require('../express');
-const log4js = require('log4js');
-const proxyaddr = require('proxy-addr');
-const settings = require('../../utils/Settings');
-const socketio = require('socket.io');
-const socketIORouter = require('../../handler/SocketIORouter');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const padMessageHandler = require('../../handler/PadMessageHandler');
+import events from 'events';
+import {sessionMiddleware} from '../express';
+import log4js from 'log4js';
+import proxyaddr from 'proxy-addr';
+import {socketIo, socketTransportProtocols, trustProxy} from '../../utils/Settings';
+import socketio from 'socket.io';
+import {addComponent, setSocketIO} from '../../handler/SocketIORouter';
+import hooks from '../../../static/js/pluginfw/hooks';
+import * as padMessageHandler from '../../handler/PadMessageHandler';
let io;
const logger = log4js.getLogger('socket.io');
@@ -52,7 +52,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
// transports in this list at once
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
io = socketio({
- transports: settings.socketTransportProtocols,
+ transports: socketTransportProtocols,
}).listen(args.server, {
/*
* Do not set the "io" cookie.
@@ -74,7 +74,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
* https://github.com/socketio/socket.io/issues/2276#issuecomment-147184662 (not totally true, actually, see above)
*/
cookie: false,
- maxHttpBufferSize: settings.socketIo.maxHttpBufferSize,
+ maxHttpBufferSize: socketIo.maxHttpBufferSize,
});
io.on('connect', (socket) => {
@@ -90,7 +90,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
const req = socket.request;
// Express sets req.ip but socket.io does not. Replicate Express's behavior here.
if (req.ip == null) {
- if (settings.trustProxy) {
+ if (trustProxy) {
req.ip = proxyaddr(req, args.app.get('trust proxy fn'));
} else {
req.ip = socket.handshake.address;
@@ -102,7 +102,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
req.headers.cookie = socket.handshake.query.cookie;
}
// See: https://socket.io/docs/faq/#Usage-with-express-session
- express.sessionMiddleware(req, {}, next);
+ sessionMiddleware(req, {}, next);
});
io.use((socket, next) => {
@@ -130,8 +130,8 @@ exports.expressCreateServer = (hookName, args, cb) => {
// if(settings.minify) io.enable('browser client minification');
// Initialize the Socket.IO Router
- socketIORouter.setSocketIO(io);
- socketIORouter.addComponent('pad', padMessageHandler);
+ setSocketIO(io);
+ addComponent('pad', padMessageHandler);
hooks.callAll('socketio', {app: args.app, io, server: args.server});
diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.ts
similarity index 63%
rename from src/node/hooks/express/specialpages.js
rename to src/node/hooks/express/specialpages.ts
index 4f41d8cd0..8b934d297 100644
--- a/src/node/hooks/express/specialpages.js
+++ b/src/node/hooks/express/specialpages.ts
@@ -1,14 +1,14 @@
'use strict';
-const path = require('path');
-const eejs = require('../../eejs');
-const fs = require('fs');
+import path from 'path';
+import {required} from '../../eejs';
+import fs from 'fs';
const fsp = fs.promises;
-const toolbar = require('../../utils/toolbar');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const settings = require('../../utils/Settings');
-const util = require('util');
-const webaccess = require('./webaccess');
+import {} from '../../utils/toolbar';
+import hooks from '../../../static/js/pluginfw/hooks';
+import {favicon, getEpVersion, maxAge, root, skinName} from '../../utils/Settings';
+import util from 'util';
+import {userCanModify} from './webaccess';
exports.expressPreSession = async (hookName, {app}) => {
// This endpoint is intended to conform to:
@@ -17,25 +17,25 @@ exports.expressPreSession = async (hookName, {app}) => {
res.set('Content-Type', 'application/health+json');
res.json({
status: 'pass',
- releaseId: settings.getEpVersion(),
+ releaseId: getEpVersion(),
});
});
app.get('/stats', (req, res) => {
- res.json(require('../../stats').toJSON());
+ res.json(required('../../stats').toJSON());
});
app.get('/javascript', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
+ res.send(required('ep_etherpad-lite/templates/javascript.html', {req}));
});
app.get('/robots.txt', (req, res) => {
let filePath =
- path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
+ path.join(root, 'src', 'static', 'skins', skinName, 'robots.txt');
res.sendFile(filePath, (err) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) {
- filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
+ filePath = path.join(root, 'src', 'static', 'robots.txt');
res.sendFile(filePath);
}
});
@@ -44,9 +44,9 @@ exports.expressPreSession = async (hookName, {app}) => {
app.get('/favicon.ico', (req, res, next) => {
(async () => {
const fns = [
- ...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []),
- path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'),
- path.join(settings.root, 'src', 'static', 'favicon.ico'),
+ ...(favicon ? [path.resolve(root, favicon)] : []),
+ path.join(root, 'src', 'static', 'skins', skinName, 'favicon.ico'),
+ path.join(root, 'src', 'static', 'favicon.ico'),
];
for (const fn of fns) {
try {
@@ -54,7 +54,7 @@ exports.expressPreSession = async (hookName, {app}) => {
} catch (err) {
continue;
}
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
await util.promisify(res.sendFile.bind(res))(fn);
return;
}
@@ -66,13 +66,13 @@ exports.expressPreSession = async (hookName, {app}) => {
exports.expressCreateServer = (hookName, args, cb) => {
// serve index.html under /
args.app.get('/', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
+ res.send(required('ep_etherpad-lite/templates/index.html', {req}));
});
// serve pad.html under /p
args.app.get('/p/:pad', (req, res, next) => {
// The below might break for pads being rewritten
- const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
+ const isReadOnly = !userCanModify(req.params.pad, req);
hooks.callAll('padInitToolbar', {
toolbar,
@@ -81,7 +81,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
// can be removed when require-kernel is dropped
res.header('Feature-Policy', 'sync-xhr \'self\'');
- res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
+ res.send(required('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
@@ -94,7 +94,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
toolbar,
});
- res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
+ res.send(required('ep_etherpad-lite/templates/timeslider.html', {
req,
toolbar,
}));
diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.ts
similarity index 86%
rename from src/node/hooks/express/static.js
rename to src/node/hooks/express/static.ts
index 26c18995a..962b845f5 100644
--- a/src/node/hooks/express/static.js
+++ b/src/node/hooks/express/static.ts
@@ -19,8 +19,9 @@ const getTar = async () => {
};
const tarJson = await fs.readFile(path.join(settings.root, 'src/node/utils/tar.json'), 'utf8');
const tar = {};
- for (const [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) {
- const files = relativeFiles.map(prefixLocalLibraryPath);
+ for (let [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) {
+ const relativeFilesMapped = relativeFiles as string[];
+ const files = relativeFilesMapped.map(prefixLocalLibraryPath);
tar[prefixLocalLibraryPath(key)] = files
.concat(files.map((p) => p.replace(/\.js$/, '')))
.concat(files.map((p) => `${p.replace(/\.js$/, '')}/index.js`));
@@ -28,7 +29,7 @@ const getTar = async () => {
return tar;
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
// Cache both minified and static.
const assetCache = new CachingMiddleware();
app.all(/\/javascripts\/(.*)/, assetCache.handle.bind(assetCache));
@@ -62,8 +63,9 @@ exports.expressPreSession = async (hookName, {app}) => {
const clientParts = plugins.parts.filter((part) => part.client_hooks != null);
const clientPlugins = {};
for (const name of new Set(clientParts.map((part) => part.plugin))) {
- clientPlugins[name] = {...plugins.plugins[name]};
- delete clientPlugins[name].package;
+ const mappedName = name as any
+ clientPlugins[mappedName] = {...plugins.plugins[mappedName]};
+ delete clientPlugins[mappedName].package;
}
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.ts
similarity index 81%
rename from src/node/hooks/express/tests.js
rename to src/node/hooks/express/tests.ts
index 66b47d2af..3b9db80b6 100644
--- a/src/node/hooks/express/tests.js
+++ b/src/node/hooks/express/tests.ts
@@ -1,11 +1,14 @@
'use strict';
-const path = require('path');
-const fsp = require('fs').promises;
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const sanitizePathname = require('../../utils/sanitizePathname');
-const settings = require('../../utils/Settings');
+import path from 'path';
+import {promises as fsp} from "fs";
+import plugins from "../../../static/js/pluginfw/plugin_defs";
+
+import sanitizePathname from "../../utils/sanitizePathname";
+
+import {enableAdminUITests, root} from "../../utils/Settings";
+import {Presession} from "../../models/Presession";
// Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/'
// instead of path.sep to separate pathname components.
const findSpecs = async (specDir) => {
@@ -29,16 +32,17 @@ const findSpecs = async (specDir) => {
return specs;
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
app.get('/tests/frontend/frontendTestSpecs.json', (req, res, next) => {
(async () => {
const modules = [];
await Promise.all(Object.entries(plugins.plugins).map(async ([plugin, def]) => {
- let {package: {path: pluginPath}} = def;
+ const mappedDef = def as Presession;
+ let {package: {path: pluginPath}} = mappedDef;
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
- if (plugin === 'ep_etherpad-lite' && !settings.enableAdminUITests &&
+ if (plugin === 'ep_etherpad-lite' && !enableAdminUITests &&
spec.startsWith('admin')) continue;
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
}
@@ -57,7 +61,7 @@ exports.expressPreSession = async (hookName, {app}) => {
})().catch((err) => next(err || new Error(err)));
});
- const rootTestFolder = path.join(settings.root, 'src/tests/frontend/');
+ const rootTestFolder = path.join(root, 'src/tests/frontend/');
app.get('/tests/frontend/index.html', (req, res) => {
res.redirect(['./', ...req.url.split('?').slice(1)].join('?'));
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.ts
similarity index 85%
rename from src/node/hooks/express/webaccess.js
rename to src/node/hooks/express/webaccess.ts
index 81ed69b07..58ecdace2 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.ts
@@ -1,12 +1,17 @@
'use strict';
-const assert = require('assert').strict;
-const log4js = require('log4js');
-const httpLogger = log4js.getLogger('http');
-const settings = require('../../utils/Settings');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const readOnlyManager = require('../../db/ReadOnlyManager');
+import {strict as assert} from "assert";
+import log4js from "log4js";
+
+import {requireAuthentication, requireAuthorization, setUsers, users} from "../../utils/Settings";
+
+import hooks from "../../../static/js/pluginfw/hooks";
+
+import {getPadId, isReadOnlyId} from "../../db/ReadOnlyManager";
+import {UserIndexedModel} from "../../models/UserIndexedModel";
+
+const httpLogger = log4js.getLogger('http');
hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
// Promisified wrapper around hooks.aCallFirst.
@@ -17,7 +22,7 @@ const aCallFirst = (hookName, context, pred = null) => new Promise((resolve, rej
const aCallFirst0 =
async (hookName, context, pred = null) => (await aCallFirst(hookName, context, pred))[0];
-exports.normalizeAuthzLevel = (level) => {
+export const normalizeAuthzLevel = (level) => {
if (!level) return false;
switch (level) {
case true:
@@ -32,20 +37,20 @@ exports.normalizeAuthzLevel = (level) => {
return false;
};
-exports.userCanModify = (padId, req) => {
- if (readOnlyManager.isReadOnlyId(padId)) return false;
- if (!settings.requireAuthentication) return true;
- const {session: {user} = {}} = req;
+export const userCanModify = (padId, req) => {
+ if (isReadOnlyId(padId)) return false;
+ if (!requireAuthentication) return true;
+ const {session: {user} = {}}:SessionSocketModel = req;
if (!user || user.readOnly) return false;
assert(user.padAuthorizations); // This is populated even if !settings.requireAuthorization.
- const level = exports.normalizeAuthzLevel(user.padAuthorizations[padId]);
+ const level = normalizeAuthzLevel(user.padAuthorizations[padId]);
return level && level !== 'readOnly';
};
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
-exports.authnFailureDelayMs = 1000;
+export const authnFailureDelayMs = 1000;
-const checkAccess = async (req, res, next) => {
+export const checkAccess = async (req, res, next) => {
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
// ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -88,16 +93,16 @@ const checkAccess = async (req, res, next) => {
// authentication is checked and once after (if settings.requireAuthorization is true).
const authorize = async () => {
const grant = async (level) => {
- level = exports.normalizeAuthzLevel(level);
+ level = normalizeAuthzLevel(level);
if (!level) return false;
const user = req.session.user;
if (user == null) return true; // This will happen if authentication is not required.
const encodedPadId = (req.path.match(/^\/p\/([^/]*)/) || [])[1];
if (encodedPadId == null) return true;
let padId = decodeURIComponent(encodedPadId);
- if (readOnlyManager.isReadOnlyId(padId)) {
+ if (isReadOnlyId(padId)) {
// pad is read-only, first get the real pad ID
- padId = await readOnlyManager.getPadId(padId);
+ padId = await getPadId(padId);
if (padId == null) return false;
}
// The user was granted access to a pad. Remember the authorization level in the user's
@@ -108,11 +113,11 @@ const checkAccess = async (req, res, next) => {
};
const isAuthenticated = req.session && req.session.user;
if (isAuthenticated && req.session.user.is_admin) return await grant('create');
- const requireAuthn = requireAdmin || settings.requireAuthentication;
+ const requireAuthn = requireAdmin || requireAuthentication;
if (!requireAuthn) return await grant('create');
if (!isAuthenticated) return await grant(false);
if (requireAdmin && !req.session.user.is_admin) return await grant(false);
- if (!settings.requireAuthorization) return await grant('create');
+ if (!requireAuthorization) return await grant('create');
return await grant(await aCallFirst0('authorize', {req, res, next, resource: req.path}));
};
@@ -131,8 +136,10 @@ const checkAccess = async (req, res, next) => {
// page).
// ///////////////////////////////////////////////////////////////////////////////////////////////
- if (settings.users == null) settings.users = {};
- const ctx = {req, res, users: settings.users, next};
+ if (users == null) setUsers({});
+ const ctx = {req, res, users: users, next, username: undefined,
+ password: undefined
+ };
// If the HTTP basic auth header is present, extract the username and password so it can be given
// to authn plugins.
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
@@ -148,7 +155,7 @@ const checkAccess = async (req, res, next) => {
}
if (!(await aCallFirst0('authenticate', ctx))) {
// Fall back to HTTP basic auth.
- const {[ctx.username]: {password} = {}} = settings.users;
+ const {[ctx.username]: {password} = {password: undefined}}:UserIndexedModel = users;
if (!httpBasicAuth || !ctx.username || password == null || password !== ctx.password) {
httpLogger.info(`Failed authentication from IP ${req.ip}`);
if (await aCallFirst0('authnFailure', {req, res})) return;
@@ -156,14 +163,14 @@ const checkAccess = async (req, res, next) => {
// No plugin handled the authentication failure. Fall back to basic authentication.
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
// Delay the error response for 1s to slow down brute force attacks.
- await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
+ await new Promise((resolve) => setTimeout(resolve, authnFailureDelayMs));
res.status(401).send('Authentication Required');
return;
}
- settings.users[ctx.username].username = ctx.username;
+ users[ctx.username].username = ctx.username;
// Make a shallow copy so that the password property can be deleted (to prevent it from
// appearing in logs or in the database) without breaking future authentication attempts.
- req.session.user = {...settings.users[ctx.username]};
+ req.session.user = {...users[ctx.username]};
delete req.session.user.password;
}
if (req.session.user == null) {
@@ -190,6 +197,7 @@ const checkAccess = async (req, res, next) => {
* Express middleware to authenticate the user and check authorization. Must be installed after the
* express-session middleware.
*/
-exports.checkAccess = (req, res, next) => {
+// FIXME why same method twice?
+export const checkAccess2 = (req, res, next) => {
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
};
diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.ts
similarity index 79%
rename from src/node/hooks/i18n.js
rename to src/node/hooks/i18n.ts
index c54348867..f6bf38f70 100644
--- a/src/node/hooks/i18n.js
+++ b/src/node/hooks/i18n.ts
@@ -1,12 +1,13 @@
'use strict';
-const languages = require('languages4translatewiki');
-const fs = require('fs');
-const path = require('path');
-const _ = require('underscore');
-const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js');
-const existsSync = require('../utils/path_exists');
-const settings = require('../utils/Settings');
+import languages from 'languages4translatewiki';
+import fs from 'fs';
+import path from 'path';
+import _ from 'underscore';
+import pluginDefs from '../../static/js/pluginfw/plugin_defs.js';
+import {check} from '../utils/path_exists';
+import {customLocaleStrings, maxAge, root} from '../utils/Settings';
+import {Presession} from "../models/Presession";
// returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...}
@@ -17,7 +18,7 @@ const getAllLocales = () => {
// into `locales2paths` (files from various dirs are grouped by lang code)
// (only json files with valid language code as name)
const extractLangs = (dir) => {
- if (!existsSync(dir)) return;
+ if (!check(dir)) return;
let stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
@@ -37,13 +38,14 @@ const getAllLocales = () => {
};
// add core supported languages first
- extractLangs(path.join(settings.root, 'src/locales'));
+ extractLangs(path.join(root, 'src/locales'));
// add plugins languages (if any)
- for (const {package: {path: pluginPath}} of Object.values(pluginDefs.plugins)) {
+ for (const val of Object.values(pluginDefs.plugins)) {
+ const pluginPath:Presession = val as Presession
// plugin locales should overwrite etherpad's core locales
- if (pluginPath.endsWith('/ep_etherpad-lite') === true) continue;
- extractLangs(path.join(pluginPath, 'locales'));
+ if (pluginPath.package.path.endsWith('/ep_etherpad-lite') === true) continue;
+ extractLangs(path.join(pluginPath.package.path, 'locales'));
}
// Build a locale index (merge all locale data other than user-supplied overrides)
@@ -68,9 +70,9 @@ const getAllLocales = () => {
const wrongFormatErr = Error(
'customLocaleStrings in wrong format. See documentation ' +
'for Customization for Administrators, under Localization.');
- if (settings.customLocaleStrings) {
- if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
- _.each(settings.customLocaleStrings, (overrides, langcode) => {
+ if (customLocaleStrings) {
+ if (typeof customLocaleStrings !== 'object') throw wrongFormatErr;
+ _.each(customLocaleStrings, (overrides, langcode) => {
if (typeof overrides !== 'object') throw wrongFormatErr;
_.each(overrides, (localeString, key) => {
if (typeof localeString !== 'string') throw wrongFormatErr;
@@ -112,7 +114,7 @@ exports.expressPreSession = async (hookName, {app}) => {
// works with /locale/en and /locale/en.json requests
const locale = req.params.locale.split('.')[0];
if (Object.prototype.hasOwnProperty.call(exports.availableLangs, locale)) {
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
} else {
@@ -121,7 +123,7 @@ exports.expressPreSession = async (hookName, {app}) => {
});
app.get('/locales.json', (req, res) => {
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(localeIndex);
});
diff --git a/src/node/models/CMDOptions.ts b/src/node/models/CMDOptions.ts
new file mode 100644
index 000000000..925170e1d
--- /dev/null
+++ b/src/node/models/CMDOptions.ts
@@ -0,0 +1,11 @@
+export type CMDOptions = {
+ cwd?: string,
+ stdio?: string|any[],
+ env?: NodeJS.ProcessEnv
+}
+
+export type CMDPromise = {
+ stdout: string,
+ stderr: string,
+ child: any
+}
diff --git a/src/node/models/LogLevel.ts b/src/node/models/LogLevel.ts
new file mode 100644
index 000000000..bd007c23f
--- /dev/null
+++ b/src/node/models/LogLevel.ts
@@ -0,0 +1 @@
+export type LogLevel = "DEBUG"|"INFO"|"WARN"|"ERROR"
diff --git a/src/node/models/PadDiffModel.ts b/src/node/models/PadDiffModel.ts
new file mode 100644
index 000000000..c201b1fb4
--- /dev/null
+++ b/src/node/models/PadDiffModel.ts
@@ -0,0 +1,3 @@
+export type PadDiffModel = {
+ _authors: any[]
+}
diff --git a/src/node/models/Presession.ts b/src/node/models/Presession.ts
new file mode 100644
index 000000000..bd8146bc3
--- /dev/null
+++ b/src/node/models/Presession.ts
@@ -0,0 +1,5 @@
+export type Presession = {
+ package:{
+ path: string,
+ }
+}
diff --git a/src/node/models/SessionSocketModel.ts b/src/node/models/SessionSocketModel.ts
index d2713d11d..ff0c730aa 100644
--- a/src/node/models/SessionSocketModel.ts
+++ b/src/node/models/SessionSocketModel.ts
@@ -3,6 +3,8 @@ type SessionSocketModel = {
user?: {
username?: string,
is_admin?: boolean
+ readOnly?: boolean,
+ padAuthorizations?: any
}
}
}
diff --git a/src/node/models/UserIndexedModel.ts b/src/node/models/UserIndexedModel.ts
new file mode 100644
index 000000000..0c808a41b
--- /dev/null
+++ b/src/node/models/UserIndexedModel.ts
@@ -0,0 +1,5 @@
+export type UserIndexedModel = {
+ [key: string]: {
+ password?: string|undefined,
+ }
+}
diff --git a/src/node/server.ts b/src/node/server.ts
index cc23c7b35..2797c8d74 100644
--- a/src/node/server.ts
+++ b/src/node/server.ts
@@ -203,11 +203,13 @@ export const stop = async () => {
} catch (err) {
logger.error('Error occurred while stopping Etherpad');
state = State.STATE_TRANSITION_FAILED;
+ // @ts-ignore
stopDoneGate.resolve();
return await exit(err);
}
logger.info('Etherpad stopped');
state = State.STOPPED;
+ // @ts-ignore
stopDoneGate.resolve();
};
diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.ts
similarity index 80%
rename from src/node/utils/Abiword.js
rename to src/node/utils/Abiword.ts
index 1ed487ae1..b83fc6a3d 100644
--- a/src/node/utils/Abiword.js
+++ b/src/node/utils/Abiword.ts
@@ -19,20 +19,21 @@
* limitations under the License.
*/
-const spawn = require('child_process').spawn;
-const async = require('async');
-const settings = require('./Settings');
-const os = require('os');
+import {spawn} from "child_process";
+
+import async from 'async';
+import {abiword} from './Settings';
+import os from 'os';
// on windows we have to spawn a process for each convertion,
// cause the plugin abicommand doesn't exist on this platform
if (os.type().indexOf('Windows') > -1) {
exports.convertFile = async (srcFile, destFile, type) => {
- const abiword = spawn(settings.abiword, [`--to=${destFile}`, srcFile]);
+ const abiword2 = spawn(abiword, [`--to=${destFile}`, srcFile]);
let stdoutBuffer = '';
- abiword.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
- abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
- await new Promise((resolve, reject) => {
+ abiword2.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
+ abiword2.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
+ await new Promise((resolve, reject) => {
abiword.on('exit', (code) => {
if (code !== 0) return reject(new Error(`Abiword died with exit code ${code}`));
if (stdoutBuffer !== '') {
@@ -46,14 +47,14 @@ if (os.type().indexOf('Windows') > -1) {
// communicate with it via stdin/stdout
// thats much faster, about factor 10
} else {
- let abiword;
+ let abiword2;
let stdoutCallback = null;
const spawnAbiword = () => {
- abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
+ abiword2 = spawn(abiword, ['--plugin', 'AbiCommand']);
let stdoutBuffer = '';
let firstPrompt = true;
- abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
- abiword.on('exit', (code) => {
+ abiword2.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
+ abiword2.on('exit', (code) => {
spawnAbiword();
if (stdoutCallback != null) {
stdoutCallback(new Error(`Abiword died with exit code ${code}`));
diff --git a/src/node/utils/AbsolutePaths.js b/src/node/utils/AbsolutePaths.ts
similarity index 94%
rename from src/node/utils/AbsolutePaths.js
rename to src/node/utils/AbsolutePaths.ts
index 73a96bb67..5356c5564 100644
--- a/src/node/utils/AbsolutePaths.js
+++ b/src/node/utils/AbsolutePaths.ts
@@ -19,9 +19,10 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-const path = require('path');
-const _ = require('underscore');
+import log4js from 'log4js';
+import path from "path";
+
+import _ from "underscore";
const absPathLogger = log4js.getLogger('AbsolutePaths');
@@ -75,7 +76,7 @@ const popIfEndsWith = (stringArray, lastDesiredElements) => {
* @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application.
*/
-exports.findEtherpadRoot = () => {
+export const findEtherpadRoot = () => {
if (etherpadRoot != null) {
return etherpadRoot;
}
@@ -131,12 +132,12 @@ exports.findEtherpadRoot = () => {
* it is returned unchanged. Otherwise it is interpreted
* relative to exports.root.
*/
-exports.makeAbsolute = (somePath) => {
+export const makeAbsolute = (somePath) => {
if (path.isAbsolute(somePath)) {
return somePath;
}
- const rewrittenPath = path.join(exports.findEtherpadRoot(), somePath);
+ const rewrittenPath = path.join(findEtherpadRoot(), somePath);
absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`);
return rewrittenPath;
@@ -150,7 +151,7 @@ exports.makeAbsolute = (somePath) => {
* a subdirectory of the base one
* @return {boolean}
*/
-exports.isSubdir = (parent, arbitraryDir) => {
+export const isSubdir = (parent, arbitraryDir) => {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir);
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
diff --git a/src/node/utils/Cli.js b/src/node/utils/Cli.ts
similarity index 96%
rename from src/node/utils/Cli.js
rename to src/node/utils/Cli.ts
index a5cdee83a..946fd66ca 100644
--- a/src/node/utils/Cli.js
+++ b/src/node/utils/Cli.ts
@@ -21,9 +21,8 @@
*/
// An object containing the parsed command-line options
-exports.argv = {};
+export const argv = process.argv.slice(2);
-const argv = process.argv.slice(2);
let arg, prevArg;
// Loop through args
diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.ts
similarity index 84%
rename from src/node/utils/ExportEtherpad.js
rename to src/node/utils/ExportEtherpad.ts
index e20739ad3..d548590bd 100644
--- a/src/node/utils/ExportEtherpad.js
+++ b/src/node/utils/ExportEtherpad.ts
@@ -14,17 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {Stream} from "./Stream";
-const Stream = require('./Stream');
-const assert = require('assert').strict;
-const authorManager = require('../db/AuthorManager');
-const hooks = require('../../static/js/pluginfw/hooks');
-const padManager = require('../db/PadManager');
+import {strict as assert} from "assert";
-exports.getPadRaw = async (padId, readOnlyId) => {
+import {getAuthor} from "../db/AuthorManager";
+
+import hooks from "../../static/js/pluginfw/hooks";
+
+import {getPad} from "../db/PadManager";
+
+export const getPadRaw = async (padId, readOnlyId) => {
const dstPfx = `pad:${readOnlyId || padId}`;
const [pad, customPrefixes] = await Promise.all([
- padManager.getPad(padId),
+ getPad(padId),
hooks.aCallAll('exportEtherpadAdditionalContent'),
]);
const pluginRecords = await Promise.all(customPrefixes.map(async (customPrefix) => {
@@ -43,7 +46,7 @@ exports.getPadRaw = async (padId, readOnlyId) => {
const records = (function* () {
for (const authorId of pad.getAllAuthors()) {
yield [`globalAuthor:${authorId}`, (async () => {
- const authorEntry = await authorManager.getAuthor(authorId);
+ const authorEntry = await getAuthor(authorId);
if (!authorEntry) return undefined; // Becomes unset when converted to JSON.
if (authorEntry.padIDs) authorEntry.padIDs = readOnlyId || padId;
return authorEntry;
diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.ts
similarity index 85%
rename from src/node/utils/ExportHelper.js
rename to src/node/utils/ExportHelper.ts
index 7962476e8..b4a5765ee 100644
--- a/src/node/utils/ExportHelper.js
+++ b/src/node/utils/ExportHelper.ts
@@ -19,11 +19,10 @@
* limitations under the License.
*/
-const AttributeMap = require('../../static/js/AttributeMap');
-const Changeset = require('../../static/js/Changeset');
+import AttributeMap from '../../static/js/AttributeMap';
+import Changeset from '../../static/js/Changeset';
-exports.getPadPlainText = (pad, revNum) => {
- const _analyzeLine = exports._analyzeLine;
+export const getPadPlainText = (pad, revNum) => {
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@@ -45,8 +44,14 @@ exports.getPadPlainText = (pad, revNum) => {
};
-exports._analyzeLine = (text, aline, apool) => {
- const line = {};
+export const _analyzeLine = (text, aline, apool) => {
+ const line = {
+ listLevel: undefined,
+ text: undefined,
+ listTypeName: undefined,
+ aline: undefined,
+ start: undefined
+ };
// identify list
let lineMarker = 0;
@@ -81,5 +86,5 @@ exports._analyzeLine = (text, aline, apool) => {
};
-exports._encodeWhitespace =
+export const _encodeWhitespace =
(s) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `${c.codePointAt(0)};`);
diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.ts
similarity index 95%
rename from src/node/utils/ExportHtml.js
rename to src/node/utils/ExportHtml.ts
index d14f40e6e..c8a7d6884 100644
--- a/src/node/utils/ExportHtml.js
+++ b/src/node/utils/ExportHtml.ts
@@ -15,16 +15,19 @@
* limitations under the License.
*/
-const Changeset = require('../../static/js/Changeset');
-const attributes = require('../../static/js/attributes');
-const padManager = require('../db/PadManager');
-const _ = require('underscore');
-const Security = require('../../static/js/security');
-const hooks = require('../../static/js/pluginfw/hooks');
-const eejs = require('../eejs');
-const _analyzeLine = require('./ExportHelper')._analyzeLine;
-const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
-const padutils = require('../../static/js/pad_utils').padutils;
+import Changeset from '../../static/js/Changeset';
+import attributes from "../../static/js/attributes";
+
+import {getPad} from "../db/PadManager";
+
+import _ from "underscore";
+
+import Security from '../../static/js/security';
+import hooks from '../../static/js/pluginfw/hooks';
+import {required} from '../eejs';
+import {_analyzeLine, _encodeWhitespace} from "./ExportHelper";
+
+import {padutils} from "../../static/js/pad_utils";
const getPadHTML = async (pad, revNum) => {
let atext = pad.atext;
@@ -38,7 +41,7 @@ const getPadHTML = async (pad, revNum) => {
return await getHTMLFromAtext(pad, atext);
};
-const getHTMLFromAtext = async (pad, atext, authorColors) => {
+export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@@ -456,8 +459,8 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
return pieces.join('');
};
-exports.getPadHTMLDocument = async (padId, revNum, readOnlyId) => {
- const pad = await padManager.getPad(padId);
+export const getPadHTMLDocument = async (padId, revNum, readOnlyId?) => {
+ const pad = await getPad(padId);
// Include some Styles into the Head for Export
let stylesForExportCSS = '';
@@ -472,7 +475,7 @@ exports.getPadHTMLDocument = async (padId, revNum, readOnlyId) => {
html += hookHtml;
}
- return eejs.require('ep_etherpad-lite/templates/export_html.html', {
+ return required('ep_etherpad-lite/templates/export_html.html', {
body: html,
padId: Security.escapeHTML(readOnlyId || padId),
extraCSS: stylesForExportCSS,
@@ -525,7 +528,4 @@ const _processSpaces = (s) => {
}
}
return parts.join('');
-};
-
-exports.getPadHTML = getPadHTML;
-exports.getHTMLFromAtext = getHTMLFromAtext;
+}
diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.ts
similarity index 97%
rename from src/node/utils/ExportTxt.js
rename to src/node/utils/ExportTxt.ts
index 9511dd0e7..7915725fa 100644
--- a/src/node/utils/ExportTxt.js
+++ b/src/node/utils/ExportTxt.ts
@@ -39,7 +39,7 @@ const getPadTXT = async (pad, revNum) => {
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
-const getTXTFromAtext = (pad, atext, authorColors) => {
+export const getTXTFromAtext = (pad, atext, authorColors?) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@@ -56,7 +56,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
});
const getLineTXT = (text, attribs) => {
- const propVals = [false, false, false];
+ const propVals:(boolean|number)[] = [false, false, false];
const ENTER = 1;
const STAY = 2;
const LEAVE = 0;
@@ -256,9 +256,8 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
return pieces.join('');
};
-exports.getTXTFromAtext = getTXTFromAtext;
-exports.getPadTXTDocument = async (padId, revNum) => {
+export const getPadTXTDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum);
};
diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.ts
similarity index 86%
rename from src/node/utils/ImportEtherpad.js
rename to src/node/utils/ImportEtherpad.ts
index da7e750ff..09dca8ecd 100644
--- a/src/node/utils/ImportEtherpad.js
+++ b/src/node/utils/ImportEtherpad.ts
@@ -16,19 +16,21 @@
* limitations under the License.
*/
-const AttributePool = require('../../static/js/AttributePool');
-const {Pad} = require('../db/Pad');
-const Stream = require('./Stream');
-const authorManager = require('../db/AuthorManager');
-const db = require('../db/DB');
-const hooks = require('../../static/js/pluginfw/hooks');
-const log4js = require('log4js');
-const supportedElems = require('../../static/js/contentcollector').supportedElems;
-const ueberdb = require('ueberdb2');
+import {AttributePool} from '../../static/js/AttributePool';
+import {Pad} from '../db/Pad';
+import {Stream} from './Stream';
+import {addPad, doesAuthorExist} from '../db/AuthorManager';
+import {db} from '../db/DB';
+import hooks from '../../static/js/pluginfw/hooks';
+import log4js from "log4js";
+
+import {supportedElems} from "../../static/js/contentcollector";
+
+import ueberdb from 'ueberdb2';
const logger = log4js.getLogger('ImportEtherpad');
-exports.setPadRaw = async (padId, r, authorId = '') => {
+export const setPadRaw = async (padId, r, authorId = '') => {
const records = JSON.parse(r);
// get supported block Elements from plugins, we will use this later.
@@ -69,7 +71,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
throw new TypeError('globalAuthor padIDs subkey is not a string');
}
checkOriginalPadId(value.padIDs);
- if (await authorManager.doesAuthorExist(id)) {
+ if (await doesAuthorExist(id)) {
existingAuthors.add(id);
return;
}
@@ -115,7 +117,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
const writeOps = (function* () {
for (const [k, v] of data) yield db.set(k, v);
- for (const a of existingAuthors) yield authorManager.addPad(a, padId);
+ for (const a of existingAuthors) yield addPad(a as string, padId);
})();
for (const op of new Stream(writeOps).batch(100).buffer(99)) await op;
};
diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.ts
similarity index 92%
rename from src/node/utils/ImportHtml.js
rename to src/node/utils/ImportHtml.ts
index d7b2172b0..2e5de910a 100644
--- a/src/node/utils/ImportHtml.js
+++ b/src/node/utils/ImportHtml.ts
@@ -15,15 +15,15 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-const Changeset = require('../../static/js/Changeset');
-const contentcollector = require('../../static/js/contentcollector');
-const jsdom = require('jsdom');
+import log4js from 'log4js';
+import Changeset from '../../static/js/Changeset';
+import contentcollector from '../../static/js/contentcollector';
+import jsdom from 'jsdom';
const apiLogger = log4js.getLogger('ImportHtml');
let processor;
-exports.setPadHTML = async (pad, html, authorId = '') => {
+export const setPadHTML = async (pad, html, authorId = '') => {
if (processor == null) {
const [{rehype}, {default: minifyWhitespace}] =
await Promise.all([import('rehype'), import('rehype-minify-whitespace')]);
@@ -90,4 +90,4 @@ exports.setPadHTML = async (pad, html, authorId = '') => {
apiLogger.debug(`The changeset: ${theChangeset}`);
await pad.setText('\n', authorId);
await pad.appendRevision(theChangeset, authorId);
-};
+}
diff --git a/src/node/utils/LibreOffice.js b/src/node/utils/LibreOffice.ts
similarity index 91%
rename from src/node/utils/LibreOffice.js
rename to src/node/utils/LibreOffice.ts
index 339209194..dddaf071d 100644
--- a/src/node/utils/LibreOffice.js
+++ b/src/node/utils/LibreOffice.ts
@@ -17,20 +17,23 @@
* limitations under the License.
*/
-const async = require('async');
-const fs = require('fs').promises;
-const log4js = require('log4js');
-const os = require('os');
-const path = require('path');
-const runCmd = require('./run_cmd');
-const settings = require('./Settings');
+import async from 'async';
+import log4js from 'log4js';
+import {promises as fs} from "fs";
+
+import os from 'os';
+import path from "path";
+
+import {exportCMD} from "./run_cmd";
+
+import {soffice} from "./Settings";
const logger = log4js.getLogger('LibreOffice');
const doConvertTask = async (task) => {
const tmpDir = os.tmpdir();
- const p = runCmd([
- settings.soffice,
+ const p = await exportCMD([
+ soffice,
'--headless',
'--invisible',
'--nologo',
@@ -81,7 +84,7 @@ const queue = async.queue(doConvertTask, 1);
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
-exports.convertFile = async (srcFile, destFile, type) => {
+export const convertFile = async (srcFile, destFile, type) => {
// Used for the moving of the file, not the conversion
const fileExtension = type;
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.ts
similarity index 91%
rename from src/node/utils/Minify.js
rename to src/node/utils/Minify.ts
index 2e8a2d960..03a7a403e 100644
--- a/src/node/utils/Minify.js
+++ b/src/node/utils/Minify.ts
@@ -21,21 +21,28 @@
* limitations under the License.
*/
-const settings = require('./Settings');
-const fs = require('fs').promises;
-const path = require('path');
-const plugins = require('../../static/js/pluginfw/plugin_defs');
-const RequireKernel = require('etherpad-require-kernel');
-const mime = require('mime-types');
-const Threads = require('threads');
-const log4js = require('log4js');
+import {maxAge, root} from './Settings';
+import {promises as fs} from "fs";
+
+import path from "path";
+
+import plugins from "../../static/js/pluginfw/plugin_defs";
+
+import RequireKernel from "etherpad-require-kernel";
+
+import mime from "mime-types";
+
+import Threads from "threads";
+
+import log4js from "log4js";
+
const sanitizePathname = require('./sanitizePathname');
const logger = log4js.getLogger('Minify');
-const ROOT_DIR = path.join(settings.root, 'src/static/');
+const ROOT_DIR = path.join(root, 'src/static/');
-const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
+const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
const LIBRARY_WHITELIST = [
'async',
@@ -89,7 +96,7 @@ const requestURI = async (url, method, headers) => {
return await p;
};
-const requestURIs = (locations, method, headers, callback) => {
+export const requestURIs = (locations, method, headers, callback) => {
Promise.all(locations.map(async (loc) => {
try {
return await requestURI(loc, method, headers);
@@ -176,10 +183,10 @@ const minify = async (req, res) => {
date.setMilliseconds(0);
res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString());
- if (settings.maxAge !== undefined) {
- const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
+ if (maxAge !== undefined) {
+ const expiresDate = new Date(Date.now() + maxAge * 1000);
res.setHeader('expires', expiresDate.toUTCString());
- res.setHeader('cache-control', `max-age=${settings.maxAge}`);
+ res.setHeader('cache-control', `max-age=${maxAge}`);
}
}
@@ -271,7 +278,7 @@ const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`
const getFileCompressed = async (filename, contentType) => {
let content = await getFile(filename);
- if (!content || !settings.minify) {
+ if (!content || !minify) {
return content;
} else if (contentType === 'application/javascript') {
return await new Promise((resolve) => {
@@ -317,10 +324,8 @@ const getFile = async (filename) => {
return await fs.readFile(path.resolve(ROOT_DIR, filename));
};
-exports.minify = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));
+export const minifyExp = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));
-exports.requestURIs = requestURIs;
-
-exports.shutdown = async (hookName, context) => {
+export const shutdown = async (hookName, context) => {
await threadsPool.terminate();
};
diff --git a/src/node/utils/MinifyWorker.js b/src/node/utils/MinifyWorker.ts
similarity index 80%
rename from src/node/utils/MinifyWorker.js
rename to src/node/utils/MinifyWorker.ts
index 364ecc96c..f5307cc9a 100644
--- a/src/node/utils/MinifyWorker.js
+++ b/src/node/utils/MinifyWorker.ts
@@ -3,11 +3,14 @@
* Worker thread to minify JS & CSS files out of the main NodeJS thread
*/
-const CleanCSS = require('clean-css');
-const Terser = require('terser');
-const fsp = require('fs').promises;
-const path = require('path');
-const Threads = require('threads');
+import CleanCSS from 'clean-css';
+import Terser from "terser";
+
+import {promises as fsp} from "fs";
+
+import path from "path";
+
+import Threads from "threads";
const compressJS = (content) => Terser.minify(content);
diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts
index 0d5f1e53c..2813e5030 100644
--- a/src/node/utils/Settings.ts
+++ b/src/node/utils/Settings.ts
@@ -27,6 +27,8 @@
* limitations under the License.
*/
+import exp from "constants";
+
const absolutePaths = require('./AbsolutePaths');
const deepEqual = require('fast-deep-equal/es6');
import fs from 'fs';
@@ -35,6 +37,7 @@ import path from 'path';
const argv = require('./Cli').argv;
import jsonminify from 'jsonminify';
import log4js from 'log4js';
+import {LogLevel} from "../models/LogLevel";
const randomString = require('./randomstring');
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
'suppressErrorsInPadText to true in your settings.json\n';
@@ -70,7 +73,7 @@ initLogging(defaultLogLevel, defaultLogConfig());
/* Root path of the installation */
export const root = absolutePaths.findEtherpadRoot();
logger.info('All relative paths will be interpreted relative to the identified ' +
- `Etherpad base dir: ${exports.root}`);
+ `Etherpad base dir: ${root}`);
export const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
export const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
@@ -93,14 +96,14 @@ export const favicon = null;
* Initialized to null, so we can spot an old configuration file and invite the
* user to update it before falling back to the default.
*/
-export const skinName = null;
+export let skinName = null;
export const skinVariants = 'super-light-toolbar super-light-editor light-background';
/**
* The IP ep-lite should listen to
*/
-export const ip = '0.0.0.0';
+export const ip:String = '0.0.0.0';
/**
* The Port ep-lite should listen to
@@ -118,6 +121,12 @@ export const suppressErrorsInPadText = false;
*/
export const ssl = false;
+export const sslKeys = {
+ cert: undefined,
+ key: undefined,
+ ca: undefined,
+}
+
/**
* socket.io transport methods
**/
@@ -142,12 +151,12 @@ export const dbType = 'dirty';
/**
* This setting is passed with dbType to ueberDB to set up the database
*/
-export const dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
+export const dbSettings = {filename: path.join(root, 'var/dirty.db')};
/**
* The default Text of a new pad
*/
-export const defaultPadText = [
+export let defaultPadText = [
'Welcome to Etherpad!',
'',
'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' +
@@ -244,12 +253,12 @@ export const minify = true;
/**
* The path of the abiword executable
*/
-export const abiword = null;
+export let abiword = null;
/**
* The path of the libreoffice executable
*/
-export const soffice = null;
+export let soffice = null;
/**
* The path of the tidy executable
@@ -264,7 +273,7 @@ export const allowUnknownFileEnds = true;
/**
* The log level of log4js
*/
-export const loglevel = defaultLogLevel;
+export const loglevel:LogLevel = defaultLogLevel;
/**
* Disable IP logging
@@ -299,7 +308,7 @@ export const logconfig = defaultLogConfig();
/*
* Session Key, do not sure this.
*/
-export const sessionKey = false;
+export let sessionKey: string|boolean = false;
/*
* Trust Proxy, whether or not trust the x-forwarded-for header.
@@ -333,7 +342,7 @@ export const cookie = {
*/
export const requireAuthentication = false;
export const requireAuthorization = false;
-export const users = {};
+export let users = {};
/*
* Show settings in admin page, by default it is true
@@ -384,6 +393,10 @@ export const exposeVersion = false;
*/
export const customLocaleStrings = {};
+export const setUsers = (newUsers:any) => {
+ users = newUsers;
+}
+
/*
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
* limited.
@@ -434,7 +447,7 @@ export const enableAdminUITests = false;
// checks if abiword is avaiable
export const abiwordAvailable = () => {
- if (exports.abiword != null) {
+ if (abiword != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
@@ -442,7 +455,7 @@ export const abiwordAvailable = () => {
};
export const sofficeAvailable = () => {
- if (exports.soffice != null) {
+ if (soffice != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
@@ -450,7 +463,7 @@ export const sofficeAvailable = () => {
};
export const exportAvailable = () => {
- const abiword = exports.abiwordAvailable();
+ const abiword = abiwordAvailable();
const soffice = sofficeAvailable();
if (abiword === 'no' && soffice === 'no') {
@@ -467,7 +480,7 @@ export const exportAvailable = () => {
export const getGitCommit = () => {
let version = '';
try {
- let rootPath = exports.root;
+ let rootPath = root;
if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim();
@@ -735,88 +748,90 @@ export const reloadSettings = () => {
storeSettings(settings);
storeSettings(credentials);
- initLogging(exports.loglevel, exports.logconfig);
+ initLogging(loglevel, logconfig);
- if (!exports.skinName) {
+ if (!skinName) {
logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
'update your settings.json. Falling back to the default "colibris".');
- exports.skinName = 'colibris';
+ skinName = 'colibris';
}
// checks if skinName has an acceptable value, otherwise falls back to "colibris"
- if (exports.skinName) {
- const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
- const countPieces = exports.skinName.split(path.sep).length;
+ if (skinName) {
+ const skinBasePath = path.join(root, 'src', 'static', 'skins');
+ const countPieces = skinName.split(path.sep).length;
if (countPieces !== 1) {
logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` +
- `not valid: "${exports.skinName}". Falling back to the default "colibris".`);
+ `not valid: "${skinName}". Falling back to the default "colibris".`);
- exports.skinName = 'colibris';
+ skinName = 'colibris';
}
// informative variable, just for the log messages
- let skinPath = path.join(skinBasePath, exports.skinName);
+ let skinPath = path.join(skinBasePath, skinName);
// what if someone sets skinName == ".." or "."? We catch him!
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` +
'Falling back to the default "colibris".');
- exports.skinName = 'colibris';
- skinPath = path.join(skinBasePath, exports.skinName);
+ skinName = 'colibris';
+ skinPath = path.join(skinBasePath, skinName);
}
if (fs.existsSync(skinPath) === false) {
logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
- exports.skinName = 'colibris';
- skinPath = path.join(skinBasePath, exports.skinName);
+ skinName = 'colibris';
+ skinPath = path.join(skinBasePath, skinName);
}
- logger.info(`Using skin "${exports.skinName}" in dir: ${skinPath}`);
+ logger.info(`Using skin "${skinName}" in dir: ${skinPath}`);
}
- if (exports.abiword) {
+ if (abiword) {
// Check abiword actually exists
- if (exports.abiword != null) {
- fs.exists(exports.abiword, (exists: boolean) => {
+ if (abiword != null) {
+ fs.exists(abiword, (exists: boolean) => {
if (!exists) {
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
}
- logger.error(`${abiwordError} File location: ${exports.abiword}`);
- exports.abiword = null;
+ logger.error(`${abiwordError} File location: ${abiword}`);
+ abiword = null;
}
});
}
}
- if (exports.soffice) {
- fs.exists(exports.soffice, (exists: boolean) => {
+ if (soffice) {
+ fs.exists(soffice, (exists: boolean) => {
if (!exists) {
const sofficeError =
'soffice (libreoffice) does not exist at this path, check your settings file.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
}
- logger.error(`${sofficeError} File location: ${exports.soffice}`);
- exports.soffice = null;
+ logger.error(`${sofficeError} File location: ${soffice}`);
+ soffice = null;
}
});
}
- if (!exports.sessionKey) {
+ if (!sessionKey) {
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
try {
- exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
+ sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
logger.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch (e) {
logger.info(
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
- exports.sessionKey = randomString(32);
- fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8');
+ sessionKey = randomString(32);
+ // FIXME Check out why this can be string boolean or Array
+ // @ts-ignore
+ fs.writeFileSync(sessionkeyFilename, sessionKey, 'utf8');
}
} else {
logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' +
@@ -825,17 +840,17 @@ export const reloadSettings = () => {
'Interface then you can ignore this message.');
}
- if (exports.dbType === 'dirty') {
+ if (dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
}
- exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
- logger.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
+ dbSettings.filename = absolutePaths.makeAbsolute(dbSettings.filename);
+ logger.warn(`${dirtyWarning} File location: ${dbSettings.filename}`);
}
- if (exports.ip === '') {
+ if (ip === '') {
// using Unix socket for connectivity
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
'"port" parameter will be interpreted as the path to a Unix socket to bind at.');
@@ -852,7 +867,7 @@ export const reloadSettings = () => {
* ACHTUNG: this may prevent caching HTTP proxies to work
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
*/
- logger.info(`Random string used for versioning assets: ${exports.randomVersionString}`);
+ logger.info(`Random string used for versioning assets: ${randomVersionString}`);
};
exports.exportedForTestingOnly = {
@@ -860,6 +875,6 @@ exports.exportedForTestingOnly = {
};
// initially load settings
-exports.reloadSettings();
+reloadSettings();
diff --git a/src/node/utils/Stream.js b/src/node/utils/Stream.ts
similarity index 98%
rename from src/node/utils/Stream.js
rename to src/node/utils/Stream.ts
index 611b83b33..0a083af84 100644
--- a/src/node/utils/Stream.js
+++ b/src/node/utils/Stream.ts
@@ -4,7 +4,9 @@
* Wrapper around any iterable that adds convenience methods that standard JavaScript iterable
* objects lack.
*/
-class Stream {
+export class Stream {
+ private readonly _iter: any;
+ private _next: null;
/**
* @returns {Stream} A Stream that yields values in the half-open range [start, end).
*/
@@ -130,5 +132,3 @@ class Stream {
*/
[Symbol.iterator]() { return this._iter; }
}
-
-module.exports = Stream;
diff --git a/src/node/utils/TidyHtml.js b/src/node/utils/TidyHtml.ts
similarity index 83%
rename from src/node/utils/TidyHtml.js
rename to src/node/utils/TidyHtml.ts
index 5b48cdbad..cbedc8056 100644
--- a/src/node/utils/TidyHtml.js
+++ b/src/node/utils/TidyHtml.ts
@@ -3,16 +3,17 @@
* Tidy up the HTML in a given file
*/
-const log4js = require('log4js');
-const settings = require('./Settings');
-const spawn = require('child_process').spawn;
+import log4js from 'log4js';
+import {tidyHtml} from "./Settings";
+
+import {spawn} from "child_process";
exports.tidy = (srcFile) => {
const logger = log4js.getLogger('TidyHtml');
return new Promise((resolve, reject) => {
// Don't do anything if Tidy hasn't been enabled
- if (!settings.tidyHtml) {
+ if (!tidyHtml) {
logger.debug('tidyHtml has not been configured yet, ignoring tidy request');
return resolve(null);
}
@@ -21,7 +22,7 @@ exports.tidy = (srcFile) => {
// Spawn a new tidy instance that cleans up the file inline
logger.debug(`Tidying ${srcFile}`);
- const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
+ const tidy = spawn(tidyHtml, ['-modify', srcFile]);
// Keep track of any error messages
tidy.stderr.on('data', (data) => {
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.ts
similarity index 90%
rename from src/node/utils/caching_middleware.js
rename to src/node/utils/caching_middleware.ts
index 3cc4daf27..93512e1ee 100644
--- a/src/node/utils/caching_middleware.js
+++ b/src/node/utils/caching_middleware.ts
@@ -16,15 +16,20 @@
* limitations under the License.
*/
-const Buffer = require('buffer').Buffer;
-const fs = require('fs');
-const fsp = fs.promises;
-const path = require('path');
-const zlib = require('zlib');
-const settings = require('./Settings');
-const existsSync = require('./path_exists');
-const util = require('util');
+import {Buffer} from "buffer";
+import fs, {Stats} from "fs";
+
+import path from "path";
+
+import {check} from './path_exists'
+import zlib from "zlib";
+
+import {root} from "./Settings";
+
+import util from "util";
+
+const fsp = fs.promises;
/*
* The crypto module can be absent on reduced node installations.
*
@@ -45,8 +50,8 @@ try {
_crypto = undefined;
}
-let CACHE_DIR = path.join(settings.root, 'var/');
-CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
+let CACHE_DIR = path.join(root, 'var/');
+CACHE_DIR = check(CACHE_DIR) ? CACHE_DIR : undefined;
const responseCache = {};
@@ -78,7 +83,7 @@ if (_crypto) {
should replace this.
*/
-module.exports = class CachingMiddleware {
+export class CachingMiddleware {
handle(req, res, next) {
this._handle(req, res, next).catch((err) => next(err || new Error(err)));
}
@@ -88,8 +93,15 @@ module.exports = class CachingMiddleware {
return next(undefined, req, res);
}
- const oldReq = {};
- const oldRes = {};
+ const oldReq = {
+ method: undefined
+ };
+ const oldRes = {
+ write: undefined,
+ end: undefined,
+ setHeader: undefined,
+ writeHead: undefined
+ };
const supportsGzip =
(req.get('Accept-Encoding') || '').indexOf('gzip') !== -1;
@@ -100,7 +112,7 @@ module.exports = class CachingMiddleware {
const stats = await fsp.stat(`${CACHE_DIR}minified_${cacheKey}`).catch(() => {});
const modifiedSince =
req.headers['if-modified-since'] && new Date(req.headers['if-modified-since']);
- if (stats != null && stats.mtime && responseCache[cacheKey]) {
+ if (stats != null && stats instanceof Object && "mtime" in stats && responseCache[cacheKey]) {
req.headers['if-modified-since'] = stats.mtime.toUTCString();
} else {
delete req.headers['if-modified-since'];
@@ -200,4 +212,4 @@ module.exports = class CachingMiddleware {
next(undefined, req, res);
}
-};
+}
diff --git a/src/node/utils/customError.js b/src/node/utils/customError.ts
similarity index 87%
rename from src/node/utils/customError.js
rename to src/node/utils/customError.ts
index 24ad181e6..79cbbd47c 100644
--- a/src/node/utils/customError.js
+++ b/src/node/utils/customError.ts
@@ -7,7 +7,9 @@
* @class CustomError
* @extends {Error}
*/
-class CustomError extends Error {
+export class CustomError extends Error {
+ code: any;
+ signal: any;
/**
* Creates an instance of CustomError.
* @param {*} message
@@ -20,5 +22,3 @@ class CustomError extends Error {
Error.captureStackTrace(this, this.constructor);
}
}
-
-module.exports = CustomError;
diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.ts
similarity index 94%
rename from src/node/utils/padDiff.js
rename to src/node/utils/padDiff.ts
index 4ab276b4b..da8ff6877 100644
--- a/src/node/utils/padDiff.js
+++ b/src/node/utils/padDiff.ts
@@ -1,11 +1,12 @@
'use strict';
-const AttributeMap = require('../../static/js/AttributeMap');
-const Changeset = require('../../static/js/Changeset');
-const attributes = require('../../static/js/attributes');
-const exportHtml = require('./ExportHtml');
+import AttributeMap from '../../static/js/AttributeMap';
+import Changeset from '../../static/js/Changeset';
+import attributes from '../../static/js/attributes';
+import {getHTMLFromAtext} from './ExportHtml';
+import {PadDiffModel} from "ep_etherpad-lite/node/models/PadDiffModel";
-function PadDiff(pad, fromRev, toRev) {
+export const PadDiff = (pad, fromRev, toRev)=> {
// check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad');
@@ -14,10 +15,16 @@ function PadDiff(pad, fromRev, toRev) {
const range = pad.getValidRevisionRange(fromRev, toRev);
if (!range) throw new Error(`Invalid revision range. startRev: ${fromRev} endRev: ${toRev}`);
+ // FIXME How to fix this?
+ // @ts-ignore
this._pad = pad;
+ // @ts-ignore
this._fromRev = range.startRev;
+ // @ts-ignore
this._toRev = range.endRev;
+ // @ts-ignore
this._html = null;
+ // @ts-ignore
this._authors = [];
}
@@ -108,9 +115,11 @@ PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) {
return {changesets, authors};
};
-PadDiff.prototype._addAuthors = function (authors) {
- const self = this;
-
+PadDiff.prototype._addAuthors = (authors)=> {
+ let self: undefined|PadDiffModel = this;
+ if(!self){
+ self = {_authors: []}
+ }
// add to array if not in the array
authors.forEach((author) => {
if (self._authors.indexOf(author) === -1) {
@@ -187,7 +196,7 @@ PadDiff.prototype.getHtml = async function () {
const authorColors = await this._pad.getAllAuthorColors();
// convert the atext to html
- this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors);
+ this._html = await getHTMLFromAtext(this._pad, atext, authorColors);
return this._html;
};
@@ -198,6 +207,10 @@ PadDiff.prototype.getAuthors = async function () {
if (this._html == null) {
await this.getHtml();
}
+ let self: undefined|PadDiffModel = this;
+ if(!self){
+ self = {_authors: []}
+ }
return self._authors;
};
diff --git a/src/node/utils/path_exists.js b/src/node/utils/path_exists.ts
similarity index 71%
rename from src/node/utils/path_exists.js
rename to src/node/utils/path_exists.ts
index 0b4c8fe94..91b6dd127 100644
--- a/src/node/utils/path_exists.js
+++ b/src/node/utils/path_exists.ts
@@ -1,7 +1,7 @@
'use strict';
-const fs = require('fs');
+import fs from 'fs';
-const check = (path) => {
+export const check = (path) => {
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
let result;
@@ -11,6 +11,4 @@ const check = (path) => {
result = false;
}
return result;
-};
-
-module.exports = check;
+}
diff --git a/src/node/utils/promises.js b/src/node/utils/promises.ts
similarity index 93%
rename from src/node/utils/promises.js
rename to src/node/utils/promises.ts
index bc9f8c2dc..9ebb07da4 100644
--- a/src/node/utils/promises.js
+++ b/src/node/utils/promises.ts
@@ -7,7 +7,7 @@
// `predicate`. Resolves to `undefined` if none of the Promises satisfy `predicate`, or if
// `promises` is empty. If `predicate` is nullish, the truthiness of the resolved value is used as
// the predicate.
-exports.firstSatisfies = (promises, predicate) => {
+export const firstSatisfies = (promises, predicate) => {
if (predicate == null) predicate = (x) => x;
// Transform each original Promise into a Promise that never resolves if the original resolved
@@ -42,7 +42,7 @@ exports.firstSatisfies = (promises, predicate) => {
// `total` is greater than `concurrency`, then `concurrency` Promises will be created right away,
// and each remaining Promise will be created once one of the earlier Promises resolves.) This async
// function resolves once all `total` Promises have resolved.
-exports.timesLimit = async (total, concurrency, promiseCreator) => {
+export const timesLimit = async (total, concurrency, promiseCreator) => {
if (total > 0 && concurrency <= 0) throw new RangeError('concurrency must be positive');
let next = 0;
const addAnother = () => promiseCreator(next++).finally(() => {
@@ -55,11 +55,14 @@ exports.timesLimit = async (total, concurrency, promiseCreator) => {
await Promise.all(promises);
};
+
/**
* An ordinary Promise except the `resolve` and `reject` executor functions are exposed as
* properties.
*/
-class Gate extends Promise {
+//FIXME Why is the constructor diviating from the Promise constructor?
+// @ts-ignore
+export class Gate extends Promise {
// Coax `.then()` into returning an ordinary Promise, not a Gate. See
// https://stackoverflow.com/a/65669070 for the rationale.
static get [Symbol.species]() { return Promise; }
@@ -73,4 +76,3 @@ class Gate extends Promise {
Object.assign(this, props);
}
}
-exports.Gate = Gate;
diff --git a/src/node/utils/randomstring.js b/src/node/utils/randomstring.ts
similarity index 100%
rename from src/node/utils/randomstring.js
rename to src/node/utils/randomstring.ts
diff --git a/src/node/utils/run_cmd.js b/src/node/utils/run_cmd.ts
similarity index 81%
rename from src/node/utils/run_cmd.js
rename to src/node/utils/run_cmd.ts
index bf5515c84..716e528b4 100644
--- a/src/node/utils/run_cmd.js
+++ b/src/node/utils/run_cmd.ts
@@ -1,12 +1,12 @@
'use strict';
-const spawn = require('cross-spawn');
-const log4js = require('log4js');
-const path = require('path');
-const settings = require('./Settings');
-
+import spawn from 'cross-spawn';
+import log4js from 'log4js';
+import path from 'path';
+import {root} from "./Settings";
+import {CMDOptions, CMDPromise} from '../models/CMDOptions'
const logger = log4js.getLogger('runCmd');
-
+import {CustomError} from './customError'
const logLines = (readable, logLineFn) => {
readable.setEncoding('utf8');
// The process won't necessarily write full lines every time -- it might write a part of a line
@@ -69,33 +69,40 @@ const logLines = (readable, logLineFn) => {
* - `stderr`: Similar to `stdout` but for stderr.
* - `child`: The ChildProcess object.
*/
-module.exports = exports = (args, opts = {}) => {
+export const exportCMD: (args: string[], opts:CMDOptions)=>void = async (args, opts = {
+ cwd: undefined,
+ stdio: undefined,
+ env: undefined
+}) => {
logger.debug(`Executing command: ${args.join(' ')}`);
-
- opts = {cwd: settings.root, ...opts};
+ opts = {cwd: root, ...opts};
logger.debug(`cwd: ${opts.cwd}`);
// Log stdout and stderr by default.
const stdio =
Array.isArray(opts.stdio) ? opts.stdio.slice() // Copy to avoid mutating the caller's array.
- : typeof opts.stdio === 'function' ? [null, opts.stdio, opts.stdio]
- : opts.stdio === 'string' ? [null, 'string', 'string']
- : Array(3).fill(opts.stdio);
+ : typeof opts.stdio === 'function' ? [null, opts.stdio, opts.stdio]
+ : opts.stdio === 'string' ? [null, 'string', 'string']
+ : Array(3).fill(opts.stdio);
const cmdLogger = log4js.getLogger(`runCmd|${args[0]}`);
- if (stdio[1] == null) stdio[1] = (line) => cmdLogger.info(line);
- if (stdio[2] == null) stdio[2] = (line) => cmdLogger.error(line);
+ if (stdio[1] == null && stdio instanceof Array) stdio[1] = (line) => cmdLogger.info(line);
+ if (stdio[2] == null && stdio instanceof Array) stdio[2] = (line) => cmdLogger.error(line);
const stdioLoggers = [];
const stdioSaveString = [];
for (const fd of [1, 2]) {
if (typeof stdio[fd] === 'function') {
stdioLoggers[fd] = stdio[fd];
- stdio[fd] = 'pipe';
+ if (stdio instanceof Array)
+ stdio[fd] = 'pipe';
} else if (stdio[fd] === 'string') {
stdioSaveString[fd] = true;
- stdio[fd] = 'pipe';
+ if (stdio instanceof Array)
+ stdio[fd] = 'pipe';
}
}
- opts.stdio = stdio;
+ if (opts.stdio instanceof Array) {
+ opts.stdio = stdio;
+ }
// On Windows the PATH environment var might be spelled "Path".
const pathVarName =
@@ -107,8 +114,8 @@ module.exports = exports = (args, opts = {}) => {
opts.env = {
...env, // Copy env to avoid modifying process.env or the caller's supplied env.
[pathVarName]: [
- path.join(settings.root, 'src', 'node_modules', '.bin'),
- path.join(settings.root, 'node_modules', '.bin'),
+ path.join(root, 'src', 'node_modules', '.bin'),
+ path.join(root, 'node_modules', '.bin'),
...(PATH ? PATH.split(path.delimiter) : []),
].join(path.delimiter),
};
@@ -116,13 +123,15 @@ module.exports = exports = (args, opts = {}) => {
// Create an error object to use in case the process fails. This is done here rather than in the
// process's `exit` handler so that we get a useful stack trace.
- const procFailedErr = new Error();
+ const procFailedErr:CustomError = new CustomError({});
const proc = spawn(args[0], args.slice(1), opts);
const streams = [undefined, proc.stdout, proc.stderr];
let px;
- const p = new Promise((resolve, reject) => { px = {resolve, reject}; });
+ const p = await new Promise((resolve, reject) => {
+ px = {resolve, reject};
+ });
[, p.stdout, p.stderr] = streams;
p.child = proc;
@@ -132,6 +141,8 @@ module.exports = exports = (args, opts = {}) => {
if (stdioLoggers[fd] != null) {
logLines(streams[fd], stdioLoggers[fd]);
} else if (stdioSaveString[fd]) {
+ //FIXME How to solve this?
+ // @ts-ignore
p[[null, 'stdout', 'stderr'][fd]] = stdioStringPromises[fd] = (async () => {
const chunks = [];
for await (const chunk of streams[fd]) chunks.push(chunk);
diff --git a/src/node/utils/sanitizePathname.js b/src/node/utils/sanitizePathname.ts
similarity index 94%
rename from src/node/utils/sanitizePathname.js
rename to src/node/utils/sanitizePathname.ts
index 61b611166..b9e492661 100644
--- a/src/node/utils/sanitizePathname.js
+++ b/src/node/utils/sanitizePathname.ts
@@ -1,10 +1,10 @@
'use strict';
-const path = require('path');
+import path from 'path';
// Normalizes p and ensures that it is a relative path that does not reach outside. See
// https://nvd.nist.gov/vuln/detail/CVE-2015-3297 for additional context.
-module.exports = (p, pathApi = path) => {
+export default (p, pathApi = path) => {
// The documentation for path.normalize() says that it resolves '..' and '.' segments. The word
// "resolve" implies that it examines the filesystem to resolve symbolic links, so 'a/../b' might
// not be the same thing as 'b'. Most path normalization functions from other libraries (e.g.,
@@ -20,4 +20,4 @@ module.exports = (p, pathApi = path) => {
// pathname would not be normalized away before being converted to '../'.
if (pathApi.sep === '\\') p = p.replace(/\\/g, '/');
return p;
-};
+}
diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.ts
similarity index 98%
rename from src/node/utils/toolbar.js
rename to src/node/utils/toolbar.ts
index 40a476878..57aed942f 100644
--- a/src/node/utils/toolbar.js
+++ b/src/node/utils/toolbar.ts
@@ -2,7 +2,7 @@
/**
* The Toolbar Module creates and renders the toolbars and buttons
*/
-const _ = require('underscore');
+import _ from 'underscore';
const removeItem = (array, what) => {
let ax;
@@ -12,13 +12,13 @@ const removeItem = (array, what) => {
return array;
};
-const defaultButtonAttributes = (name, overrides) => ({
+const defaultButtonAttributes = (name, overrides?) => ({
command: name,
localizationId: `pad.toolbar.${name}.title`,
class: `buttonicon buttonicon-${name}`,
});
-const tag = (name, attributes, contents) => {
+const tag = (name, attributes, contents?) => {
const aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) {
diff --git a/src/package-lock.json b/src/package-lock.json
index 34b695265..dd27c26e2 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -58,6 +58,8 @@
},
"devDependencies": {
"@types/express": "4.17.17",
+ "@types/jquery": "^3.5.16",
+ "@types/js-cookie": "^3.0.3",
"@types/node": "^20.3.1",
"concurrently": "^8.2.0",
"eslint": "^8.14.0",
@@ -1081,6 +1083,21 @@
"@types/unist": "*"
}
},
+ "node_modules/@types/jquery": {
+ "version": "3.5.16",
+ "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz",
+ "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==",
+ "dev": true,
+ "dependencies": {
+ "@types/sizzle": "*"
+ }
+ },
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz",
+ "integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==",
+ "dev": true
+ },
"node_modules/@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@@ -1181,6 +1198,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/sizzle": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
+ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
+ "dev": true
+ },
"node_modules/@types/stoppable": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.1.tgz",
diff --git a/src/package.json b/src/package.json
index b1062eb7b..6ade4d3d1 100644
--- a/src/package.json
+++ b/src/package.json
@@ -78,23 +78,25 @@
"etherpad-lite": "node/server.js"
},
"devDependencies": {
+ "@types/express": "4.17.17",
+ "@types/jquery": "^3.5.16",
+ "@types/js-cookie": "^3.0.3",
+ "@types/node": "^20.3.1",
+ "concurrently": "^8.2.0",
"eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13",
"etherpad-cli-client": "^2.0.1",
"mocha": "^9.2.2",
"mocha-froth": "^0.2.10",
"nodeify": "^1.0.1",
+ "nodemon": "^2.0.22",
"openapi-schema-validation": "^0.4.2",
"selenium-webdriver": "^4.10.0",
"set-cookie-parser": "^2.4.8",
"sinon": "^13.0.2",
"split-grid": "^1.0.11",
"supertest": "^6.3.3",
- "typescript": "^4.9.5",
- "@types/node": "^20.3.1",
- "@types/express": "4.17.17",
- "concurrently": "^8.2.0",
- "nodemon": "^2.0.22"
+ "typescript": "^4.9.5"
},
"engines": {
"node": ">=14.15.0",
diff --git a/src/static/js/ace2_common.js b/src/static/js/ace2_common.ts
similarity index 78%
rename from src/static/js/ace2_common.js
rename to src/static/js/ace2_common.ts
index c1dab5cfd..683dace3a 100644
--- a/src/static/js/ace2_common.js
+++ b/src/static/js/ace2_common.ts
@@ -22,11 +22,11 @@
* limitations under the License.
*/
-const isNodeText = (node) => (node.nodeType === 3);
+export const isNodeText = (node) => (node.nodeType === 3);
-const getAssoc = (obj, name) => obj[`_magicdom_${name}`];
+export const getAssoc = (obj, name) => obj[`_magicdom_${name}`];
-const setAssoc = (obj, name, value) => {
+export const setAssoc = (obj, name, value) => {
// note that in IE designMode, properties of a node can get
// copied to new nodes that are spawned during editing; also,
// properties representable in HTML text can survive copy-and-paste
@@ -38,7 +38,7 @@ const setAssoc = (obj, name, value) => {
// between false and true, a number between 0 and numItems inclusive.
-const binarySearch = (numItems, func) => {
+export const binarySearch = (numItems, func) => {
if (numItems < 1) return 0;
if (func(0)) return 0;
if (!func(numItems - 1)) return numItems;
@@ -52,17 +52,10 @@ const binarySearch = (numItems, func) => {
return high;
};
-const binarySearchInfinite = (expectedLength, func) => {
+export const binarySearchInfinite = (expectedLength, func) => {
let i = 0;
while (!func(i)) i += expectedLength;
return binarySearch(i, func);
};
-const noop = () => {};
-
-exports.isNodeText = isNodeText;
-exports.getAssoc = getAssoc;
-exports.setAssoc = setAssoc;
-exports.binarySearch = binarySearch;
-exports.binarySearchInfinite = binarySearchInfinite;
-exports.noop = noop;
+export const noop = () => {};
diff --git a/src/static/js/pad_utils.js b/src/static/js/pad_utils.ts
similarity index 92%
rename from src/static/js/pad_utils.js
rename to src/static/js/pad_utils.ts
index e10841f50..537f99169 100644
--- a/src/static/js/pad_utils.js
+++ b/src/static/js/pad_utils.ts
@@ -22,13 +22,13 @@
* limitations under the License.
*/
-const Security = require('./security');
+import Security from './security';
/**
* Generates a random String with the given length. Is needed to generate the Author, Group,
* readonly, session Ids
*/
-const randomString = (len) => {
+const randomString = (len?) => {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let randomstring = '';
len = len || 20;
@@ -91,7 +91,9 @@ const urlRegex = (() => {
// https://stackoverflow.com/a/68957976
const base64url = /^(?=(?:.{4})*$)[A-Za-z0-9_-]*(?:[AQgw]==|[AEIMQUYcgkosw048]=)?$/;
-const padutils = {
+export const padutils = {
+ setupGlobalExceptionHandler: undefined,
+
/**
* Prints a warning message followed by a stack trace (to make it easier to figure out what code
* is using the deprecated function).
@@ -107,25 +109,32 @@ const padutils = {
* @param {...*} args - Passed to `padutils.warnDeprecated.logger.warn` (or `console.warn` if no
* logger is set), with a stack trace appended if available.
*/
- warnDeprecated: (...args) => {
- if (padutils.warnDeprecated.disabledForTestingOnly) return;
+ warnDeprecated: (...args) => {
+ if ("disabledForTestingOnly" in padutils.warnDeprecated) return;
const err = new Error();
if (Error.captureStackTrace) Error.captureStackTrace(err, padutils.warnDeprecated);
err.name = '';
// Rate limit identical deprecation warnings (as determined by the stack) to avoid log spam.
- if (typeof err.stack === 'string') {
- if (padutils.warnDeprecated._rl == null) {
+ if (typeof err.stack === 'string' && "_rl" in padutils.warnDeprecated) {
padutils.warnDeprecated._rl =
{prevs: new Map(), now: () => Date.now(), period: 10 * 60 * 1000};
- }
const rl = padutils.warnDeprecated._rl;
- const now = rl.now();
- const prev = rl.prevs.get(err.stack);
- if (prev != null && now - prev < rl.period) return;
- rl.prevs.set(err.stack, now);
+ if (rl instanceof Object && "now" in rl && "prevs" in rl && "period" in rl
+ && rl.now instanceof Function && rl.prevs instanceof Map && rl.period instanceof Number){
+ const now = rl.now();
+ const prev = rl.prevs.get(err.stack);
+ if (prev != null && now - prev < rl.period) return;
+ rl.prevs.set(err.stack, now);
+ }
}
if (err.stack) args.push(err.stack);
- (padutils.warnDeprecated.logger || console).warn(...args);
+ if ("logger" in padutils.warnDeprecated && padutils.warnDeprecated.logger instanceof Object
+ && "warn" in padutils.warnDeprecated.logger && padutils.warnDeprecated.logger.warn instanceof Function){
+ padutils.warnDeprecated.logger.warn(...args)
+ }
+ else{
+ console.warn(...args)
+ }
},
escapeHtml: (x) => Security.escapeHTML(String(x)),
@@ -350,7 +359,11 @@ const padutils = {
* Returns a string that can be used in the `token` cookie as a secret that authenticates a
* particular author.
*/
- generateAuthorToken: () => `t.${randomString()}`,
+ generateAuthorToken: () => {
+ const randomAuthToken = randomString()
+
+ return `t.+${randomAuthToken}`
+ }
};
let globalExceptionHandler = null;
@@ -400,6 +413,8 @@ padutils.setupGlobalExceptionHandler = () => {
.append(txt(`UserAgent: ${navigator.userAgent}`)).append($('
')),
];
+ //FIXME gritter not defined
+ // @ts-ignore
$.gritter.add({
title: 'An error occurred',
text: errorMsg,
@@ -429,7 +444,7 @@ padutils.setupGlobalExceptionHandler = () => {
}
};
-padutils.binarySearch = require('./ace2_common').binarySearch;
+import {binarySearch} from '../../static/js/ace2_common';
// https://stackoverflow.com/a/42660748
const inThirdPartyIframe = () => {
@@ -439,11 +454,12 @@ const inThirdPartyIframe = () => {
return true;
}
};
+import cookies from 'js-cookie/dist/js.cookie';
// This file is included from Node so that it can reuse randomString, but Node doesn't have a global
// window object.
if (typeof window !== 'undefined') {
- exports.Cookies = require('js-cookie/dist/js.cookie').withAttributes({
+ cookies.withAttributes({
// Use `SameSite=Lax`, unless Etherpad is embedded in an iframe from another site in which case
// use `SameSite=None`. For iframes from another site, only `None` has a chance of working
// because the cookies are third-party (not same-site). Many browsers/users block third-party
@@ -456,5 +472,3 @@ if (typeof window !== 'undefined') {
secure: window.location.protocol === 'https:',
});
}
-exports.randomString = randomString;
-exports.padutils = padutils;
diff --git a/src/tsconfig.json b/src/tsconfig.json
index d58a717dc..b99c6c15a 100644
--- a/src/tsconfig.json
+++ b/src/tsconfig.json
@@ -1,5 +1,7 @@
{
"compilerOptions": {
+ "typeRoots": ["node_modules/@types"],
+ "types": ["node", "jquery"],
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",