mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Start server without paths.
This commit is contained in:
parent
798543fb45
commit
8926677a66
13 changed files with 2657 additions and 74 deletions
3
src/.babelrc
Normal file
3
src/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
|
@ -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 "$@"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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});
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
2568
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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\""
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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;
|
Loading…
Reference in a new issue