From 63de3c6e309deec517e2777032903cdcfecd7aee Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:41:33 +0200 Subject: [PATCH] refactor(node/hooks): partial CJS->ESM (express, i18n, 7 of 14 express/* files) Done: express.ts (incl. https/http hoisted to top-level imports, exports.server + exports.sessionMiddleware -> export let), i18n.ts, admin.ts, webaccess.ts (authnFailureDelayMs preserved as export let + setter), padurlsanitize.ts, pwa.ts, errorhandling.ts (exports.app preserved as export let), tokenTransfer.ts, adminplugins.ts. Still CJS in hooks/express/: adminsettings, apicalls, importexport, openapi, socketio, specialpages, static. --- src/node/hooks/express.ts | 51 ++++++++++++------------ src/node/hooks/express/admin.ts | 8 ++-- src/node/hooks/express/adminplugins.ts | 19 ++++----- src/node/hooks/express/errorhandling.ts | 11 ++--- src/node/hooks/express/padurlsanitize.ts | 6 +-- src/node/hooks/express/pwa.ts | 6 +-- src/node/hooks/express/tokenTransfer.ts | 6 +-- src/node/hooks/express/webaccess.ts | 28 +++++++------ src/node/hooks/i18n.ts | 14 +++---- 9 files changed, 77 insertions(+), 72 deletions(-) diff --git a/src/node/hooks/express.ts b/src/node/hooks/express.ts index 8e6f5b879..e82a07ce4 100644 --- a/src/node/hooks/express.ts +++ b/src/node/hooks/express.ts @@ -1,7 +1,7 @@ 'use strict'; import {Socket} from "node:net"; -import type {MapArrayType} from "../types/MapType"; +import type {MapArrayType} from "../types/MapType.js"; import _ from 'underscore'; import cookieParser from 'cookie-parser'; @@ -9,15 +9,17 @@ import events from 'events'; import express from 'express'; import expressSession, {Store} from 'express-session'; import fs from 'fs'; -const hooks = require('../../static/js/pluginfw/hooks'); +import hooks from '../../static/js/pluginfw/hooks.js'; import log4js from 'log4js'; -const SessionStore = require('../db/SessionStore'); -import settings, {getEpVersion, getGitCommit} from '../utils/Settings'; -const stats = require('../stats') +import SessionStore from '../db/SessionStore.js'; +import settings, {getEpVersion, getGitCommit} from '../utils/Settings.js'; +import stats from '../stats.js'; import util from 'util'; -const webaccess = require('./express/webaccess'); +import * as webaccess from './express/webaccess.js'; +import https from 'https'; +import http from 'http'; -import SecretRotator from '../security/SecretRotator'; +import SecretRotator from '../security/SecretRotator.js'; let secretRotator: SecretRotator|null = null; const logger = log4js.getLogger('http'); @@ -27,14 +29,15 @@ const sockets:Set = new Set(); const socketsEvents = new events.EventEmitter(); const startTime = stats.settableGauge('httpStartTime'); -exports.server = null; +export let server: any = null; +export let sessionMiddleware: any = null; 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 + // Call 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 @@ -53,7 +56,7 @@ const closeServer = async () => { } await p; clearTimeout(timeout); - exports.server = null; + server = null; startTime.setValue(0); logger.info('HTTP server closed'); } @@ -64,14 +67,14 @@ const closeServer = async () => { secretRotator = null; }; -exports.createServer = async () => { +export const createServer = async () => { console.log('Report bugs at https://github.com/ether/etherpad/issues'); serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`; console.log(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`); - await exports.restartServer(); + await restartServer(); if (settings.ip === '') { // using Unix socket for connectivity @@ -96,7 +99,7 @@ exports.createServer = async () => { } }; -exports.restartServer = async () => { +export const restartServer = async () => { await closeServer(); const app = express(); // New syntax for express v3 @@ -119,11 +122,9 @@ exports.restartServer = async () => { } } - 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) => { @@ -205,7 +206,7 @@ exports.restartServer = async () => { store.startCleanup(); } sessionStore = store; - exports.sessionMiddleware = expressSession({ + sessionMiddleware = expressSession({ rolling: true, secret, store: sessionStore ?? undefined, @@ -242,15 +243,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, settings}); - app.use(exports.sessionMiddleware); + app.use(sessionMiddleware); app.use(webaccess.checkAccess); 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:Socket) => { + server.on('connection', (socket:Socket) => { sockets.add(socket); socketsEvents.emit('updated'); socket.on('close', () => { @@ -258,11 +259,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)(settings.port, settings.ip); startTime.setValue(Date.now()); logger.info('HTTP server listening for connections'); }; -exports.shutdown = async (hookName:string, context: any) => { +export const shutdown = async (hookName:string, context: any) => { await closeServer(); }; diff --git a/src/node/hooks/express/admin.ts b/src/node/hooks/express/admin.ts index 7e9e6316b..6af8eb6e4 100644 --- a/src/node/hooks/express/admin.ts +++ b/src/node/hooks/express/admin.ts @@ -1,10 +1,10 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; import path from "path"; import fs from "fs"; -import {MapArrayType} from "../../types/MapType"; +import {MapArrayType} from "../../types/MapType.js"; -import settings from 'ep_etherpad-lite/node/utils/Settings'; +import settings from '../../utils/Settings.js'; const ADMIN_PATH = path.join(settings.root, 'src', 'templates'); const PROXY_HEADER = "x-proxy-path" @@ -15,7 +15,7 @@ const PROXY_HEADER = "x-proxy-path" * @param {Function} cb the callback function * @return {*} */ -exports.expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Function): any => { +export const expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Function): any => { if (!fs.existsSync(ADMIN_PATH)) { console.error('admin template not found, skipping admin interface. You need to rebuild it in /admin with pnpm run build-copy') diff --git a/src/node/hooks/express/adminplugins.ts b/src/node/hooks/express/adminplugins.ts index 47f06c513..a6bea4f6e 100644 --- a/src/node/hooks/express/adminplugins.ts +++ b/src/node/hooks/express/adminplugins.ts @@ -1,20 +1,21 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; -import {ErrorCaused} from "../../types/ErrorCaused"; -import {QueryType} from "../../types/QueryType"; +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import {ErrorCaused} from "../../types/ErrorCaused.js"; +import {QueryType} from "../../types/QueryType.js"; -import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer"; -import {PackageData, PackageInfo} from "../../types/PackageInfo"; +import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer.js"; +import {PackageData, PackageInfo} from "../../types/PackageInfo.js"; import semver from 'semver'; import log4js from 'log4js'; -import {MapArrayType} from "../../types/MapType"; +import {MapArrayType} from "../../types/MapType.js"; -const pluginDefs = require('../../../static/js/pluginfw/plugin_defs'); +import pluginDefs from '../../../static/js/pluginfw/plugin_defs.js'; +import stats from '../../stats.js'; const logger = log4js.getLogger('adminPlugins'); -exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => { +export const socketio = (hookName:string, args:ArgsExpressType, cb:Function) => { const io = args.io.of('/pluginfw/installer'); io.on('connection', (socket:any) => { // @ts-ignore @@ -41,7 +42,7 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => { socket.on('getStats', ()=>{ console.log("Getting stats for admin plugins"); - socket.emit('results:stats', require('../../stats').toJSON()); + socket.emit('results:stats', stats.toJSON()); }) socket.on('getInstalled', async (query: string) => { diff --git a/src/node/hooks/express/errorhandling.ts b/src/node/hooks/express/errorhandling.ts index 2de819b0e..4905e7f0d 100644 --- a/src/node/hooks/express/errorhandling.ts +++ b/src/node/hooks/express/errorhandling.ts @@ -1,12 +1,13 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; -import {ErrorCaused} from "../../types/ErrorCaused"; +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import {ErrorCaused} from "../../types/ErrorCaused.js"; -const stats = require('../../stats') +import stats from '../../stats.js'; -exports.expressCreateServer = (hook_name:string, args: ArgsExpressType, cb:Function) => { - exports.app = args.app; +export let app: any = null; +export const expressCreateServer = (hook_name:string, args: ArgsExpressType, cb:Function) => { + app = args.app; // Handle errors args.app.use((err:ErrorCaused, req:any, res:any, next:Function) => { diff --git a/src/node/hooks/express/padurlsanitize.ts b/src/node/hooks/express/padurlsanitize.ts index 8679bcfe3..41aaa3ea6 100644 --- a/src/node/hooks/express/padurlsanitize.ts +++ b/src/node/hooks/express/padurlsanitize.ts @@ -1,10 +1,10 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; -const padManager = require('../../db/PadManager'); +import * as padManager from '../../db/PadManager.js'; -exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => { +export const expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => { // redirects browser to the pad's sanitized url if needed. otherwise, renders the html args.app.param('pad', (req:any, res:any, next:Function, padId:string) => { (async () => { diff --git a/src/node/hooks/express/pwa.ts b/src/node/hooks/express/pwa.ts index a763af5b4..1a6eb8c8d 100644 --- a/src/node/hooks/express/pwa.ts +++ b/src/node/hooks/express/pwa.ts @@ -1,5 +1,5 @@ -import {ArgsExpressType} from "../../types/ArgsExpressType"; -import settings from '../../utils/Settings'; +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import settings from '../../utils/Settings.js'; const pwa = { name: settings.title || "Etherpad", @@ -23,7 +23,7 @@ const pwa = { background_color: "#0f775b" } -exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => { +export const expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => { args.app.get('/manifest.json', (req:any, res:any) => { res.json(pwa); }); diff --git a/src/node/hooks/express/tokenTransfer.ts b/src/node/hooks/express/tokenTransfer.ts index 5a0ccbe01..69eac1dd9 100644 --- a/src/node/hooks/express/tokenTransfer.ts +++ b/src/node/hooks/express/tokenTransfer.ts @@ -1,7 +1,7 @@ -import {ArgsExpressType} from "../../types/ArgsExpressType"; -const db = require('../../db/DB'); +import {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import db from '../../db/DB.js'; import crypto from 'crypto' -import settings from '../../utils/Settings'; +import settings from '../../utils/Settings.js'; type TokenTransferRequest = { diff --git a/src/node/hooks/express/webaccess.ts b/src/node/hooks/express/webaccess.ts index 031224f68..5bcba7868 100644 --- a/src/node/hooks/express/webaccess.ts +++ b/src/node/hooks/express/webaccess.ts @@ -2,13 +2,13 @@ import {strict as assert} from "assert"; import log4js from 'log4js'; -import {SocketClientRequest} from "../../types/SocketClientRequest"; -import {WebAccessTypes} from "../../types/WebAccessTypes"; -import {SettingsUser} from "../../types/SettingsUser"; +import {SocketClientRequest} from "../../types/SocketClientRequest.js"; +import {WebAccessTypes} from "../../types/WebAccessTypes.js"; +import {SettingsUser} from "../../types/SettingsUser.js"; const httpLogger = log4js.getLogger('http'); -import settings from '../../utils/Settings'; -const hooks = require('../../../static/js/pluginfw/hooks'); -import readOnlyManager from '../../db/ReadOnlyManager'; +import settings from '../../utils/Settings.js'; +import hooks from '../../../static/js/pluginfw/hooks.js'; +import readOnlyManager from '../../db/ReadOnlyManager.js'; hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead'; @@ -21,7 +21,7 @@ const aCallFirst0 = // @ts-ignore async (hookName: string, context:any, pred = null) => (await aCallFirst(hookName, context, pred))[0]; -exports.normalizeAuthzLevel = (level: string|boolean) => { +export const normalizeAuthzLevel = (level: string|boolean) => { if (!level) return false; switch (level) { case true: @@ -36,18 +36,19 @@ exports.normalizeAuthzLevel = (level: string|boolean) => { return false; }; -exports.userCanModify = (padId: string, req: SocketClientRequest) => { +export const userCanModify = (padId: string, req: SocketClientRequest) => { if (readOnlyManager.isReadOnlyId(padId)) return false; if (!settings.requireAuthentication) return true; const {session: {user} = {}} = 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 let authnFailureDelayMs = 1000; +export const setAuthnFailureDelayMs = (v: number) => { authnFailureDelayMs = v; }; const staticResources = [ /^\/padbootstrap-[a-zA-Z0-9]+\.min\.js$/, @@ -106,7 +107,7 @@ const checkAccess = async (req:any, res:any, next: Function) => { // authentication is checked and once after (if settings.requireAuthorization is true). const authorize = async () => { const grant = async (level: string|false) => { - 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. @@ -186,7 +187,7 @@ const checkAccess = async (req:any, res:any, next: Function) => { 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; } @@ -230,6 +231,7 @@ const checkAccess = async (req:any, res:any, next: Function) => { * Express middleware to authenticate the user and check authorization. Must be installed after the * express-session middleware. */ -exports.checkAccess = (req:any, res:any, next:Function) => { +export const checkAccessMiddleware = (req:any, res:any, next:Function) => { checkAccess(req, res, next).catch((err) => next(err || new Error(err))); }; +export { checkAccessMiddleware as checkAccess }; diff --git a/src/node/hooks/i18n.ts b/src/node/hooks/i18n.ts index a9adc190b..28bc05f2f 100644 --- a/src/node/hooks/i18n.ts +++ b/src/node/hooks/i18n.ts @@ -1,15 +1,15 @@ 'use strict'; -import type {MapArrayType} from "../types/MapType"; -import {I18nPluginDefs} from "../types/I18nPluginDefs"; +import type {MapArrayType} from "../types/MapType.js"; +import {I18nPluginDefs} from "../types/I18nPluginDefs.js"; -const languages = require('languages4translatewiki'); +import languages from 'languages4translatewiki'; import fs from 'fs'; import path from 'path'; import _ from 'underscore'; -const pluginDefs = require('../../static/js/pluginfw/plugin_defs'); -import existsSync from '../utils/path_exists'; -import settings from '../utils/Settings'; +import pluginDefs from '../../static/js/pluginfw/plugin_defs.js'; +import existsSync from '../utils/path_exists.js'; +import settings from '../utils/Settings.js'; // returns all existing messages merged together and grouped by langcode // {es: {"foo": "string"}, en:...} @@ -131,7 +131,7 @@ const generateLocaleIndex = (locales:MapArrayType) => { }; -exports.expressPreSession = async (hookName:string, {app}:any) => { +export const expressPreSession = async (hookName:string, {app}:any) => { // regenerate locales on server restart const locales = getAllLocales(); const localeIndex = generateLocaleIndex(locales);