Start server without paths.

feature/typescript
SamTV12345 2023-06-25 16:49:17 +02:00
parent 798543fb45
commit 8926677a66
No known key found for this signature in database
GPG Key ID: E63EEC7466038043
13 changed files with 2657 additions and 74 deletions

3
src/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View File

@ -32,4 +32,4 @@ src/bin/installDeps.sh "$@" || exit 1
# Move to the node folder and start
log "Starting Etherpad..."
exec node src/node/server.js "$@"
exec node src/dist/node/server.js "$@"

View File

@ -109,11 +109,15 @@ export const createServer = async () => {
'variable to production by using: export NODE_ENV=production');
}
};
export const app = express();
import http from 'http'
import https from 'https'
export const restartServer = async () => {
await closeServer();
const app = express(); // New syntax for express v3
// New syntax for express v3
if (ssl) {
console.log('SSL -- enabled');
@ -133,11 +137,8 @@ export const restartServer = async () => {
options.ca.push(fs.readFileSync(caFileName));
}
}
const https = require('https');
server = https.createServer(options, app);
} else {
const http = require('http');
server = http.createServer(app);
}

View File

@ -44,5 +44,5 @@ export const expressPreSession = async (hookName, {app}) => {
// Provide a possibility to query the latest available API version
app.get('/api', (req, res) => {
res.json({currentVersion: latestApiVersion});
});
};
})
}

View File

@ -51,7 +51,7 @@ export const userCanModify = (padId, req) => {
};
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
export const authnFailureDelayMs = 1000;
export let authnFailureDelayMs = 1000;
export const checkAccess = async (req, res, next) => {
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
@ -204,3 +204,8 @@ export const checkAccess = async (req, res, next) => {
export const checkAccess2 = (req, res, next) => {
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
};
// Setters
export const setauthnFailureDelayMs = (value) => {
authnFailureDelayMs = value;
}

View File

@ -51,9 +51,7 @@ import {createServer, server} from './hooks/express';
import hooks = require('../static/js/pluginfw/hooks');
import pluginDefs = require('../static/js/pluginfw/plugin_defs');
import plugins = require('../static/js/pluginfw/plugins');
import stats = require('./stats');
import {createCollection} from "./stats";
const logger = log4js.getLogger('server');
console.log = logger.info.bind(logger); // do the same for others - console.debug, etc.
@ -152,7 +150,7 @@ export const start = async () => {
logger.debug(`Installed parts:\n${plugins.formatParts()}`);
logger.debug(`Installed server-side hooks:\n${plugins.formatHooks('hooks', false)}`);
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll(createServer())
await hooks.aCallAll(createServer());
} catch (err) {
logger.error('Error occurred while starting Etherpad');
state = State.STATE_TRANSITION_FAILED;

View File

@ -109,12 +109,12 @@ export const skinVariants = 'super-light-toolbar super-light-editor light-backgr
/**
* The IP ep-lite should listen to
*/
export const ip:String = '0.0.0.0';
export let ip:String = '0.0.0.0';
/**
* The Port ep-lite should listen to
*/
export const port = process.env.PORT || 9001;
export let port = process.env.PORT || 9001;
/**
* Should we suppress Error messages from being in Pad Contents
@ -125,7 +125,7 @@ export const suppressErrorsInPadText = false;
* The SSL signed server key and the Certificate Authority's own certificate
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
*/
export const ssl = false;
export let ssl = false;
export const sslKeys = {
cert: undefined,
@ -319,7 +319,7 @@ export let sessionKey: string|boolean = false;
/*
* Trust Proxy, whether or not trust the x-forwarded-for header.
*/
export const trustProxy = false;
export let trustProxy = false;
/*
* Settings controlling the session cookie issued by Etherpad.
@ -412,7 +412,7 @@ export const setUsers = (newUsers:any) => {
*
* See https://github.com/nfriedly/express-rate-limit for more options
*/
export const importExportRateLimiting = {
export let importExportRateLimiting = {
// duration of the rate limit window (milliseconds)
windowMs: 90000,
@ -429,7 +429,7 @@ export const importExportRateLimiting = {
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
export const commitRateLimiting = {
export let commitRateLimiting = {
// duration of the rate limit window (seconds)
duration: 1,
@ -884,3 +884,27 @@ export const exportedForTestingOnly = {
reloadSettings();
// Setters
export const setPort = (value: number) => {
port = value;
}
export const setIp = (value: string) => {
ip = value;
}
export const setTrustProxy = (value: boolean) => {
trustProxy = value;
}
export const setSsl = (value: boolean) => {
ssl = value;
}
export const setimportExportRateLimiting = (value: any) => {
importExportRateLimiting = value;
}
export const setCommitRateLimiting = (value: any) => {
commitRateLimiting = value;
}

2568
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -78,18 +78,22 @@
"etherpad-lite": "node/server.js"
},
"devDependencies": {
"@babel/cli": "^7.22.5",
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@babel/register": "^7.22.5",
"@types/cross-spawn": "^6.0.2",
"@types/express": "4.17.17",
"@types/jquery": "^3.5.16",
"@types/js-cookie": "^3.0.3",
"i18next": "^23.2.3",
"i18next-fs-backend": "^2.1.5",
"@types/node": "^20.3.1",
"@types/underscore": "^1.11.5",
"concurrently": "^8.2.0",
"eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13",
"etherpad-cli-client": "^2.0.1",
"i18next": "^23.2.3",
"i18next-fs-backend": "^2.1.5",
"mocha": "^9.2.2",
"mocha-froth": "^0.2.10",
"nodeify": "^1.0.1",
@ -112,7 +116,7 @@
},
"scripts": {
"lint": "eslint .",
"test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
"test": "mocha --require @babel/register --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
"test-container": "mocha --timeout 5000 tests/container/specs/api",
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/server.js\""
},

View File

@ -1,6 +1,7 @@
'use strict';
import {hooks} from './plugin_defs';
import {getLogger} from "log4js";
// Maps the name of a server-side hook to a string explaining the deprecation
// (e.g., 'use the foo hook instead').
@ -347,10 +348,15 @@ const callHookFnAsync = async (hook, context) => {
// If cb is non-null, this function resolves to the value returned by cb.
export const aCallAll = async (hookName, context?, cb = null) => {
if (cb != null) return await attachCallback(aCallAll(hookName, context), cb);
if (context == null) context = {};
if (context == null) {
context = {};
}
const hooksResult = hooks[hookName] || [];
const results = await Promise.all(
hooksResult.map(async (hook) => normalizeValue(await callHookFnAsync(hook, context))));
hooksResult.map(async (hook) => {
getLogger().info(`Calling hook ${hook.hook_name} asynchronously`);
return normalizeValue(await callHookFnAsync(hook, context))
}));
return flatten1(results);
};

View File

@ -105,7 +105,6 @@ export const update = async () => {
}));
logger.info(`Loaded ${Object.keys(packages).length} plugins`);
logger.info(parts)
setPlugins(plugins);
setParts(sortParts(parts))
setHooks(extractHooks(parts, 'hooks', pathNormalization));
@ -129,12 +128,13 @@ const getPackages = async () => {
logger.info("After exportCMD")
const {dependencies = {}} = JSON.parse(cmdReturn as string);
await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => {
logger.info(`Found plugin ${pkg}`)
if (!pkg.startsWith(prefix)) {
delete dependencies[pkg];
return;
}
const mappedInfo = info as PluginInfo
logger.info(`Found plugin ${pkg} at ${mappedInfo.path}`)
mappedInfo.realPath = await fs.realpath(mappedInfo.path);
}));
return dependencies;

View File

@ -1,6 +1,7 @@
'use strict';
import {parts} from './plugin_defs';
import {getLogger} from "log4js";
const disabledHookReasons = {
hooks: {
@ -35,6 +36,7 @@ export const loadFn = (path, hookName) => {
export const extractHooks = (parts: any[], hookSetName, normalizer) => {
const hooks = {};
const logger = getLogger('pluginfw:shared')
for (const part of parts) {
for (const [hookName, regHookFnName] of Object.entries(part[hookSetName] || {})) {
/* On the server side, you can't just
@ -45,10 +47,10 @@ export const extractHooks = (parts: any[], hookSetName, normalizer) => {
const disabledReason = (disabledHookReasons[hookSetName] || {})[hookName];
if (disabledReason) {
console.error(`Hook ${hookSetName}/${hookName} is disabled. Reason: ${disabledReason}`);
console.error(`The hook function ${hookFnName} from plugin ${part.plugin} ` +
logger.error(`Hook ${hookSetName}/${hookName} is disabled. Reason: ${disabledReason}`);
logger.error(`The hook function ${hookFnName} from plugin ${part.plugin} ` +
'will never be called, which may cause the plugin to fail');
console.error(`Please update the ${part.plugin} plugin to not use the ${hookName} hook`);
logger.error(`Please update the ${part.plugin} plugin to not use the ${hookName} hook`);
return;
}
let hookFn;
@ -56,7 +58,7 @@ export const extractHooks = (parts: any[], hookSetName, normalizer) => {
hookFn = loadFn(hookFnName, hookName);
if (!hookFn) throw new Error('Not a function');
} catch (err) {
console.error(`Failed to load hook function "${hookFnName}" for plugin "${part.plugin}" ` +
logger.error(`Failed to load hook function "${hookFnName}" for plugin "${part.plugin}" ` +
`part "${part.name}" hook set "${hookSetName}" hook "${hookName}": ` +
`${err.stack || err}`);
}

View File

@ -1,22 +1,28 @@
'use strict';
const AttributePool = require('../../static/js/AttributePool');
const apiHandler = require('../../node/handler/APIHandler');
const assert = require('assert').strict;
const io = require('socket.io-client');
const log4js = require('log4js');
const {padutils} = require('../../static/js/pad_utils');
const process = require('process');
const server = require('../../node/server');
const setCookieParser = require('set-cookie-parser');
const settings = require('../../node/utils/Settings');
const supertest = require('supertest');
const webaccess = require('../../node/hooks/express/webaccess');
import {AttributePool} from '../../static/js/AttributePool';
import {exportedForTestingOnly as aExportedForTestingOnly} from '../../node/handler/APIHandler';
import assert, {strict} from 'assert'
import io from 'socket.io-client';
import log4js from 'log4js';
import {padutils} from '../../static/js/pad_utils';
import processA from 'process';
import {} from '../../node/server';
import setCookieParser from 'set-cookie-parser';
import {setCommitRateLimiting, setimportExportRateLimiting, setIp, setPort} from '../../node/utils/Settings';
import supertest from 'supertest';
import {authnFailureDelayMs, setauthnFailureDelayMs} from '../../node/hooks/express/webaccess';
import { before,after } from 'mocha';
import * as settings from '../../node/utils/Settings';
import {server} from "../../node/hooks/express";
const backups = {};
const backups:{
settings?:any,
authnFailureDelayMs?:any
} = {};
let agentPromise = null;
exports.apiKey = apiHandler.exportedForTestingOnly.apiKey;
exports.apiKey = aExportedForTestingOnly.apiKey;
exports.agent = null;
exports.baseUrl = null;
exports.httpServer = null;
@ -27,7 +33,7 @@ const logLevel = logger.level;
// Mocha doesn't monitor unhandled Promise rejections, so convert them to uncaught exceptions.
// https://github.com/mochajs/mocha/issues/2640
process.on('unhandledRejection', (reason, promise) => { throw reason; });
processA.on('unhandledRejection', (reason, promise) => { throw reason; });
before(async function () {
this.timeout(60000);
@ -42,31 +48,29 @@ exports.init = async function () {
if (!logLevel.isLessThanOrEqualTo(log4js.levels.DEBUG)) {
logger.warn('Disabling non-test logging for the duration of the test. ' +
'To enable non-test logging, change the loglevel setting to DEBUG.');
log4js.setGlobalLogLevel(log4js.levels.OFF);
logger.setLevel(logLevel);
}
// Note: This is only a shallow backup.
backups.settings = Object.assign({}, settings);
// Start the Etherpad server on a random unused port.
settings.port = 0;
settings.ip = 'localhost';
settings.importExportRateLimiting = {max: 0};
settings.commitRateLimiting = {duration: 0.001, points: 1e6};
setPort(0)
setIp('localhost')
setimportExportRateLimiting({max: 0})
setCommitRateLimiting({duration: 0.001, points: 1e6});
exports.httpServer = await server.start();
exports.baseUrl = `http://localhost:${exports.httpServer.address().port}`;
logger.debug(`HTTP server at ${exports.baseUrl}`);
// Create a supertest user agent for the HTTP server.
exports.agent = supertest(exports.baseUrl);
// Speed up authn tests.
backups.authnFailureDelayMs = webaccess.authnFailureDelayMs;
webaccess.authnFailureDelayMs = 0;
backups.authnFailureDelayMs = authnFailureDelayMs;
setauthnFailureDelayMs(0)
after(async function () {
webaccess.authnFailureDelayMs = backups.authnFailureDelayMs;
setauthnFailureDelayMs(backups.authnFailureDelayMs);
// Note: This does not unset settings that were added.
Object.assign(settings, backups.settings);
log4js.setGlobalLogLevel(logLevel);
await server.exit();
});
@ -93,7 +97,7 @@ exports.waitForSocketEvent = async (socket, event) => {
const handlers = new Map();
let cancelTimeout;
try {
const timeoutP = new Promise((resolve, reject) => {
const timeoutP = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`timed out waiting for ${event} event`));
cancelTimeout = () => {};
@ -139,9 +143,11 @@ exports.waitForSocketEvent = async (socket, event) => {
*/
exports.connect = async (res = null) => {
// Convert the `set-cookie` header(s) into a `cookie` header.
const resCookies = (res == null) ? {} : setCookieParser.parse(res, {map: true});
const reqCookieHdr = Object.entries(resCookies).map(
([name, cookie]) => `${name}=${encodeURIComponent(cookie.value)}`).join('; ');
const resCookies:{
[key:string]:{value:string}
} = (res == null) ? {} : setCookieParser.parse(res, {map: true});
const reqCookieHdr = Object.entries(resCookies)
.map(([name, cookie]) => `${name}=${encodeURIComponent(cookie.value)}`).join('; ');
logger.debug('socket.io connecting...');
let padId = null;
@ -191,7 +197,7 @@ exports.handshake = async (socket, padId, token = padutils.generateAuthorToken()
/**
* Convenience wrapper around `socket.send()` that waits for acknowledgement.
*/
exports.sendMessage = async (socket, message) => await new Promise((resolve, reject) => {
exports.sendMessage = async (socket, message) => await new Promise<void>((resolve, reject) => {
socket.send(message, (errInfo) => {
if (errInfo != null) {
const {name, message} = errInfo;