mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 00:16:15 -04:00
Added typescript to etherpad
* Fixed determining file extension. * Added ts-node * Fixed backend tests. * Fixed frontend test runs. * Fixed tests. * Use script approach for starting etherpad. * Change directory to src. * Fixed env. * Change directory * Fixed build arg. * Fixed docker build. * Fixed. * Fixed cypress file path. * Fixed. * Use latest node container. * Fixed windows workflow. * Use tsx and optimized docker image. * Added workflow for type checks. * Fixed. * Added tsconfig. * Converted more files to typescript. * Removed commented keys. * Typed caching middleware. * Added script for checking the types. * Moved SecretRotator to typescript. * Fixed npm installation and moved to types folder. * Use better scripts for watching typescript changes. * Update windows.yml * Fixed order of npm installation. * Converted i18n. * Added more types. * Added more types. * Fixed import. * Fixed tests. * Fixed tests. * Fixed type checking test. * Fixed stats * Added express types. * fixed.
This commit is contained in:
parent
c3202284bc
commit
ead3c0ea38
74 changed files with 1259 additions and 612 deletions
|
@ -1,25 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('underscore');
|
||||
const SecretRotator = require('../security/SecretRotator');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const events = require('events');
|
||||
const express = require('express');
|
||||
const expressSession = require('express-session');
|
||||
const fs = require('fs');
|
||||
import {Socket} from "node:net";
|
||||
import type {MapArrayType} from "../types/MapType";
|
||||
|
||||
import _ from 'underscore';
|
||||
// @ts-ignore
|
||||
import cookieParser from 'cookie-parser';
|
||||
import events from 'events';
|
||||
import express from 'express';
|
||||
// @ts-ignore
|
||||
import expressSession from 'express-session';
|
||||
import fs from 'fs';
|
||||
const hooks = require('../../static/js/pluginfw/hooks');
|
||||
const log4js = require('log4js');
|
||||
import log4js from 'log4js';
|
||||
const SessionStore = require('../db/SessionStore');
|
||||
const settings = require('../utils/Settings');
|
||||
const stats = require('../stats');
|
||||
const util = require('util');
|
||||
const stats = require('../stats')
|
||||
import util from 'util';
|
||||
const webaccess = require('./express/webaccess');
|
||||
|
||||
let secretRotator = null;
|
||||
import SecretRotator from '../security/SecretRotator';
|
||||
|
||||
let secretRotator: SecretRotator|null = null;
|
||||
const logger = log4js.getLogger('http');
|
||||
let serverName;
|
||||
let sessionStore;
|
||||
const sockets = new Set();
|
||||
let serverName:string;
|
||||
let sessionStore: { shutdown: () => void; } | null;
|
||||
const sockets:Set<Socket> = new Set();
|
||||
const socketsEvents = new events.EventEmitter();
|
||||
const startTime = stats.settableGauge('httpStartTime');
|
||||
|
||||
|
@ -101,7 +107,7 @@ exports.restartServer = async () => {
|
|||
console.log(`SSL -- server key file: ${settings.ssl.key}`);
|
||||
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
|
||||
|
||||
const options = {
|
||||
const options: MapArrayType<any> = {
|
||||
key: fs.readFileSync(settings.ssl.key),
|
||||
cert: fs.readFileSync(settings.ssl.cert),
|
||||
};
|
||||
|
@ -163,7 +169,7 @@ exports.restartServer = async () => {
|
|||
app.use((req, res, next) => {
|
||||
const stopWatch = stats.timer('httpRequests').start();
|
||||
const sendFn = res.send.bind(res);
|
||||
res.send = (...args) => { stopWatch.end(); sendFn(...args); };
|
||||
res.send = (...args) => { stopWatch.end(); return sendFn(...args); };
|
||||
next();
|
||||
});
|
||||
|
||||
|
@ -173,7 +179,7 @@ exports.restartServer = async () => {
|
|||
// anyway.
|
||||
if (!(settings.loglevel === 'WARN' && settings.loglevel === 'ERROR')) {
|
||||
app.use(log4js.connectLogger(logger, {
|
||||
level: log4js.levels.DEBUG,
|
||||
level: log4js.levels.DEBUG.levelStr,
|
||||
format: ':status, :method :url',
|
||||
}));
|
||||
}
|
||||
|
@ -237,7 +243,7 @@ exports.restartServer = async () => {
|
|||
hooks.aCallAll('expressConfigure', {app}),
|
||||
hooks.aCallAll('expressCreateServer', {app, server: exports.server}),
|
||||
]);
|
||||
exports.server.on('connection', (socket) => {
|
||||
exports.server.on('connection', (socket:Socket) => {
|
||||
sockets.add(socket);
|
||||
socketsEvents.emit('updated');
|
||||
socket.on('close', () => {
|
||||
|
@ -250,6 +256,6 @@ exports.restartServer = async () => {
|
|||
logger.info('HTTP server listening for connections');
|
||||
};
|
||||
|
||||
exports.shutdown = async (hookName, context) => {
|
||||
exports.shutdown = async (hookName:string, context: any) => {
|
||||
await closeServer();
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
const eejs = require('../../eejs');
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
|
||||
const eejs = require('../../eejs');
|
||||
|
||||
/**
|
||||
* Add the admin navigation link
|
||||
|
@ -9,8 +10,8 @@ const eejs = require('../../eejs');
|
|||
* @param {Function} cb the callback function
|
||||
* @return {*}
|
||||
*/
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
args.app.get('/admin', (req, res) => {
|
||||
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => {
|
||||
args.app.get('/admin', (req:any, res:any) => {
|
||||
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
|
||||
});
|
|
@ -1,5 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
import {Socket} from "node:net";
|
||||
import {ErrorCaused} from "../../types/ErrorCaused";
|
||||
import {QueryType} from "../../types/QueryType";
|
||||
import {PluginType} from "../../types/Plugin";
|
||||
|
||||
const eejs = require('../../eejs');
|
||||
const settings = require('../../utils/Settings');
|
||||
const installer = require('../../../static/js/pluginfw/installer');
|
||||
|
@ -8,8 +14,8 @@ const plugins = require('../../../static/js/pluginfw/plugins');
|
|||
const semver = require('semver');
|
||||
const UpdateCheck = require('../../utils/UpdateCheck');
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
args.app.get('/admin/plugins', (req, res) => {
|
||||
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function) => {
|
||||
args.app.get('/admin/plugins', (req:any, res:any) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
|
||||
plugins: pluginDefs.plugins,
|
||||
req,
|
||||
|
@ -17,7 +23,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
}));
|
||||
});
|
||||
|
||||
args.app.get('/admin/plugins/info', (req, res) => {
|
||||
args.app.get('/admin/plugins/info', (req:any, res:any) => {
|
||||
const gitCommit = settings.getGitCommit();
|
||||
const epVersion = settings.getEpVersion();
|
||||
|
||||
|
@ -36,13 +42,14 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
return cb();
|
||||
};
|
||||
|
||||
exports.socketio = (hookName, args, cb) => {
|
||||
exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||
const io = args.io.of('/pluginfw/installer');
|
||||
io.on('connection', (socket) => {
|
||||
io.on('connection', (socket:any) => {
|
||||
// @ts-ignore
|
||||
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
||||
if (!isAdmin) return;
|
||||
|
||||
socket.on('getInstalled', (query) => {
|
||||
socket.on('getInstalled', (query:string) => {
|
||||
// send currently installed plugins
|
||||
const installed =
|
||||
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
|
||||
|
@ -66,13 +73,14 @@ exports.socketio = (hookName, args, cb) => {
|
|||
|
||||
socket.emit('results:updatable', {updatable});
|
||||
} catch (err) {
|
||||
console.warn(err.stack || err.toString());
|
||||
const errc = err as ErrorCaused
|
||||
console.warn(errc.stack || errc.toString());
|
||||
|
||||
socket.emit('results:updatable', {updatable: {}});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('getAvailable', async (query) => {
|
||||
socket.on('getAvailable', async (query:string) => {
|
||||
try {
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
|
||||
socket.emit('results:available', results);
|
||||
|
@ -82,7 +90,7 @@ exports.socketio = (hookName, args, cb) => {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on('search', async (query) => {
|
||||
socket.on('search', async (query: QueryType) => {
|
||||
try {
|
||||
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
|
||||
let res = Object.keys(results)
|
||||
|
@ -98,8 +106,8 @@ exports.socketio = (hookName, args, cb) => {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on('install', (pluginName) => {
|
||||
installer.install(pluginName, (err) => {
|
||||
socket.on('install', (pluginName: string) => {
|
||||
installer.install(pluginName, (err: ErrorCaused) => {
|
||||
if (err) console.warn(err.stack || err.toString());
|
||||
|
||||
socket.emit('finished:install', {
|
||||
|
@ -110,8 +118,8 @@ exports.socketio = (hookName, args, cb) => {
|
|||
});
|
||||
});
|
||||
|
||||
socket.on('uninstall', (pluginName) => {
|
||||
installer.uninstall(pluginName, (err) => {
|
||||
socket.on('uninstall', (pluginName:string) => {
|
||||
installer.uninstall(pluginName, (err:ErrorCaused) => {
|
||||
if (err) console.warn(err.stack || err.toString());
|
||||
|
||||
socket.emit('finished:uninstall', {plugin: pluginName, error: err ? err.message : null});
|
||||
|
@ -128,11 +136,13 @@ exports.socketio = (hookName, args, cb) => {
|
|||
* @param {String} dir The directory of the plugin
|
||||
* @return {Object[]}
|
||||
*/
|
||||
const sortPluginList = (plugins, property, /* ASC?*/dir) => plugins.sort((a, b) => {
|
||||
const sortPluginList = (plugins:PluginType[], property:string, /* ASC?*/dir:string): object[] => plugins.sort((a, b) => {
|
||||
// @ts-ignore
|
||||
if (a[property] < b[property]) {
|
||||
return dir ? -1 : 1;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (a[property] > b[property]) {
|
||||
return dir ? 1 : -1;
|
||||
}
|
|
@ -6,8 +6,8 @@ const hooks = require('../../../static/js/pluginfw/hooks');
|
|||
const plugins = require('../../../static/js/pluginfw/plugins');
|
||||
const settings = require('../../utils/Settings');
|
||||
|
||||
exports.expressCreateServer = (hookName, {app}) => {
|
||||
app.get('/admin/settings', (req, res) => {
|
||||
exports.expressCreateServer = (hookName:string, {app}:any) => {
|
||||
app.get('/admin/settings', (req:any, res:any) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
|
||||
req,
|
||||
settings: '',
|
||||
|
@ -16,12 +16,13 @@ exports.expressCreateServer = (hookName, {app}) => {
|
|||
});
|
||||
};
|
||||
|
||||
exports.socketio = (hookName, {io}) => {
|
||||
io.of('/settings').on('connection', (socket) => {
|
||||
exports.socketio = (hookName:string, {io}:any) => {
|
||||
io.of('/settings').on('connection', (socket: any ) => {
|
||||
// @ts-ignore
|
||||
const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
|
||||
if (!isAdmin) return;
|
||||
|
||||
socket.on('load', async (query) => {
|
||||
socket.on('load', async (query:string):Promise<any> => {
|
||||
let data;
|
||||
try {
|
||||
data = await fsp.readFile(settings.settingsFilename, 'utf8');
|
||||
|
@ -36,7 +37,7 @@ exports.socketio = (hookName, {io}) => {
|
|||
}
|
||||
});
|
||||
|
||||
socket.on('saveSettings', async (newSettings) => {
|
||||
socket.on('saveSettings', async (newSettings:string) => {
|
||||
await fsp.writeFile(settings.settingsFilename, newSettings);
|
||||
socket.emit('saveprogress', 'saved');
|
||||
});
|
|
@ -6,15 +6,15 @@ const {Formidable} = require('formidable');
|
|||
const apiHandler = require('../../handler/APIHandler');
|
||||
const util = require('util');
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
// The Etherpad client side sends information about how a disconnect happened
|
||||
app.post('/ep/pad/connection-diagnostic-info', async (req, res) => {
|
||||
app.post('/ep/pad/connection-diagnostic-info', async (req:any, res:any) => {
|
||||
const [fields, files] = await (new Formidable({})).parse(req);
|
||||
clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
|
||||
res.end('OK');
|
||||
});
|
||||
|
||||
const parseJserrorForm = async (req) => {
|
||||
const parseJserrorForm = async (req:any) => {
|
||||
const form = new Formidable({
|
||||
maxFileSize: 1, // Files are not expected. Not sure if 0 means unlimited, so 1 is used.
|
||||
});
|
||||
|
@ -23,11 +23,11 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
};
|
||||
|
||||
// The Etherpad client side sends information about client side javscript errors
|
||||
app.post('/jserror', (req, res, next) => {
|
||||
app.post('/jserror', (req:any, res:any, next:Function) => {
|
||||
(async () => {
|
||||
const data = JSON.parse(await parseJserrorForm(req));
|
||||
clientLogger.warn(`${data.msg} --`, {
|
||||
[util.inspect.custom]: (depth, options) => {
|
||||
[util.inspect.custom]: (depth: number, options:any) => {
|
||||
// Depth is forced to infinity to ensure that all of the provided data is logged.
|
||||
options = Object.assign({}, options, {depth: Infinity, colors: true});
|
||||
return util.inspect(data, options);
|
||||
|
@ -38,7 +38,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
});
|
||||
|
||||
// Provide a possibility to query the latest available API version
|
||||
app.get('/api', (req, res) => {
|
||||
app.get('/api', (req:any, res:any) => {
|
||||
res.json({currentVersion: apiHandler.latestApiVersion});
|
||||
});
|
||||
};
|
|
@ -1,12 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const stats = require('../../stats');
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
import {ErrorCaused} from "../../types/ErrorCaused";
|
||||
|
||||
exports.expressCreateServer = (hook_name, args, cb) => {
|
||||
const stats = require('../../stats')
|
||||
|
||||
exports.expressCreateServer = (hook_name:string, args: ArgsExpressType, cb:Function) => {
|
||||
exports.app = args.app;
|
||||
|
||||
// Handle errors
|
||||
args.app.use((err, req, res, next) => {
|
||||
args.app.use((err:ErrorCaused, req:any, res:any, next:Function) => {
|
||||
// if an error occurs Connect will pass it down
|
||||
// through these "error-handling" middleware
|
||||
// allowing you to respond however you like
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
|
||||
const hasPadAccess = require('../../padaccess');
|
||||
const settings = require('../../utils/Settings');
|
||||
const exportHandler = require('../../handler/ExportHandler');
|
||||
|
@ -10,10 +12,10 @@ const rateLimit = require('express-rate-limit');
|
|||
const securityManager = require('../../db/SecurityManager');
|
||||
const webaccess = require('./webaccess');
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||
const limiter = rateLimit({
|
||||
...settings.importExportRateLimiting,
|
||||
handler: (request, response, next, options) => {
|
||||
handler: (request:any) => {
|
||||
if (request.rateLimit.current === request.rateLimit.limit + 1) {
|
||||
// when the rate limiter triggers, write a warning in the logs
|
||||
console.warn('Import/Export rate limiter triggered on ' +
|
||||
|
@ -24,7 +26,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
|
||||
// handle export requests
|
||||
args.app.use('/p/:pad/:rev?/export/:type', limiter);
|
||||
args.app.get('/p/:pad/:rev?/export/:type', (req, res, next) => {
|
||||
args.app.get('/p/:pad/:rev?/export/:type', (req:any, res:any, next:Function) => {
|
||||
(async () => {
|
||||
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
|
||||
// send a 404 if we don't support this filetype
|
||||
|
@ -70,8 +72,9 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
|
||||
// handle import requests
|
||||
args.app.use('/p/:pad/import', limiter);
|
||||
args.app.post('/p/:pad/import', (req, res, next) => {
|
||||
args.app.post('/p/:pad/import', (req:any, res:any, next:Function) => {
|
||||
(async () => {
|
||||
// @ts-ignore
|
||||
const {session: {user} = {}} = req;
|
||||
const {accessStatus, authorID: authorId} = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
|
@ -1,5 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
import {OpenAPIOperations, OpenAPISuccessResponse, SwaggerUIResource} from "../../types/SwaggerUIResource";
|
||||
import {MapArrayType} from "../../types/MapType";
|
||||
import {ErrorCaused} from "../../types/ErrorCaused";
|
||||
|
||||
/**
|
||||
* node/hooks/express/openapi.js
|
||||
*
|
||||
|
@ -52,8 +56,9 @@ const APIPathStyle = {
|
|||
REST: 'rest', // restful paths e.g. /rest/group/create
|
||||
};
|
||||
|
||||
|
||||
// API resources - describe your API endpoints here
|
||||
const resources = {
|
||||
const resources:SwaggerUIResource = {
|
||||
// Group
|
||||
group: {
|
||||
create: {
|
||||
|
@ -372,7 +377,7 @@ const defaultResponses = {
|
|||
},
|
||||
};
|
||||
|
||||
const defaultResponseRefs = {
|
||||
const defaultResponseRefs:OpenAPISuccessResponse = {
|
||||
200: {
|
||||
$ref: '#/components/responses/Success',
|
||||
},
|
||||
|
@ -388,16 +393,16 @@ const defaultResponseRefs = {
|
|||
};
|
||||
|
||||
// convert to a dictionary of operation objects
|
||||
const operations = {};
|
||||
const operations: OpenAPIOperations = {};
|
||||
for (const [resource, actions] of Object.entries(resources)) {
|
||||
for (const [action, spec] of Object.entries(actions)) {
|
||||
const {operationId, responseSchema, ...operation} = spec;
|
||||
const {operationId,responseSchema, ...operation} = spec;
|
||||
|
||||
// add response objects
|
||||
const responses = {...defaultResponseRefs};
|
||||
const responses:OpenAPISuccessResponse = {...defaultResponseRefs};
|
||||
if (responseSchema) {
|
||||
responses[200] = cloneDeep(defaultResponses.Success);
|
||||
responses[200].content['application/json'].schema.properties.data = {
|
||||
responses[200].content!['application/json'].schema.properties.data = {
|
||||
type: 'object',
|
||||
properties: responseSchema,
|
||||
};
|
||||
|
@ -414,7 +419,7 @@ for (const [resource, actions] of Object.entries(resources)) {
|
|||
}
|
||||
}
|
||||
|
||||
const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
||||
const generateDefinitionForVersion = (version:string, style = APIPathStyle.FLAT) => {
|
||||
const definition = {
|
||||
openapi: OPENAPI_VERSION,
|
||||
info,
|
||||
|
@ -490,7 +495,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
|
||||
// build operations
|
||||
for (const funcName of Object.keys(apiHandler.version[version])) {
|
||||
let operation = {};
|
||||
let operation:OpenAPIOperations = {};
|
||||
if (operations[funcName]) {
|
||||
operation = {...operations[funcName]};
|
||||
} else {
|
||||
|
@ -505,7 +510,9 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
operation.parameters = operation.parameters || [];
|
||||
for (const paramName of apiHandler.version[version][funcName]) {
|
||||
operation.parameters.push({$ref: `#/components/parameters/${paramName}`});
|
||||
// @ts-ignore
|
||||
if (!definition.components.parameters[paramName]) {
|
||||
// @ts-ignore
|
||||
definition.components.parameters[paramName] = {
|
||||
name: paramName,
|
||||
in: 'query',
|
||||
|
@ -525,6 +532,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
|
||||
// add to definition
|
||||
// NOTE: It may be confusing that every operation can be called with both GET and POST
|
||||
// @ts-ignore
|
||||
definition.paths[path] = {
|
||||
get: {
|
||||
...operation,
|
||||
|
@ -539,7 +547,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
return definition;
|
||||
};
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
// create openapi-backend handlers for each api version under /api/{version}/*
|
||||
for (const version of Object.keys(apiHandler.version)) {
|
||||
// we support two different styles of api: flat + rest
|
||||
|
@ -552,7 +560,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
const definition = generateDefinitionForVersion(version, style);
|
||||
|
||||
// serve version specific openapi definition
|
||||
app.get(`${apiRoot}/openapi.json`, (req, res) => {
|
||||
app.get(`${apiRoot}/openapi.json`, (req:any, res:any) => {
|
||||
// For openapi definitions, wide CORS is probably fine
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
|
@ -561,7 +569,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
// serve latest openapi definition file under /api/openapi.json
|
||||
const isLatestAPIVersion = version === apiHandler.latestApiVersion;
|
||||
if (isLatestAPIVersion) {
|
||||
app.get(`/${style}/openapi.json`, (req, res) => {
|
||||
app.get(`/${style}/openapi.json`, (req:any, res:any) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
});
|
||||
|
@ -588,12 +596,12 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
|
||||
// register operation handlers
|
||||
for (const funcName of Object.keys(apiHandler.version[version])) {
|
||||
const handler = async (c, req, res) => {
|
||||
const handler = async (c: any, req:any, res:any) => {
|
||||
// parse fields from request
|
||||
const {header, params, query} = c.request;
|
||||
|
||||
// read form data if method was POST
|
||||
let formData = {};
|
||||
let formData:MapArrayType<any> = {};
|
||||
if (c.request.method === 'post') {
|
||||
const form = new IncomingForm();
|
||||
formData = (await form.parse(req))[0];
|
||||
|
@ -615,18 +623,19 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
try {
|
||||
data = await apiHandler.handle(version, funcName, fields, req, res);
|
||||
} catch (err) {
|
||||
const errCaused = err as ErrorCaused
|
||||
// convert all errors to http errors
|
||||
if (createHTTPError.isHttpError(err)) {
|
||||
// pass http errors thrown by handler forward
|
||||
throw err;
|
||||
} else if (err.name === 'apierror') {
|
||||
} else if (errCaused.name === 'apierror') {
|
||||
// parameters were wrong and the api stopped execution, pass the error
|
||||
// convert to http error
|
||||
throw new createHTTPError.BadRequest(err.message);
|
||||
throw new createHTTPError.BadRequest(errCaused.message);
|
||||
} else {
|
||||
// an unknown error happened
|
||||
// log it and throw internal error
|
||||
logger.error(err.stack || err.toString());
|
||||
logger.error(errCaused.stack || errCaused.toString());
|
||||
throw new createHTTPError.InternalError('internal error');
|
||||
}
|
||||
}
|
||||
|
@ -649,7 +658,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
|
||||
// start and bind to express
|
||||
api.init();
|
||||
app.use(apiRoot, async (req, res) => {
|
||||
app.use(apiRoot, async (req:any, res:any) => {
|
||||
let response = null;
|
||||
try {
|
||||
if (style === APIPathStyle.REST) {
|
||||
|
@ -660,31 +669,33 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
// pass to openapi-backend handler
|
||||
response = await api.handleRequest(req, req, res);
|
||||
} catch (err) {
|
||||
const errCaused = err as ErrorCaused
|
||||
// handle http errors
|
||||
res.statusCode = err.statusCode || 500;
|
||||
// @ts-ignore
|
||||
res.statusCode = errCaused.statusCode || 500;
|
||||
|
||||
// convert to our json response format
|
||||
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
|
||||
switch (res.statusCode) {
|
||||
case 403: // forbidden
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
response = {code: 4, message: errCaused.message, data: null};
|
||||
break;
|
||||
case 401: // unauthorized (no or wrong api key)
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
response = {code: 4, message: errCaused.message, data: null};
|
||||
break;
|
||||
case 404: // not found (no such function)
|
||||
response = {code: 3, message: err.message, data: null};
|
||||
response = {code: 3, message: errCaused.message, data: null};
|
||||
break;
|
||||
case 500: // server error (internal error)
|
||||
response = {code: 2, message: err.message, data: null};
|
||||
response = {code: 2, message: errCaused.message, data: null};
|
||||
break;
|
||||
case 400: // bad request (wrong parameters)
|
||||
// respond with 200 OK to keep old behavior and pass tests
|
||||
res.statusCode = 200; // @TODO: this is bad api design
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
response = {code: 1, message: errCaused.message, data: null};
|
||||
break;
|
||||
default:
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
response = {code: 1, message: errCaused.message, data: null};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +713,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
* @param {APIPathStyle} style The style of the API path
|
||||
* @return {String} The root path for the API version
|
||||
*/
|
||||
const getApiRootForVersion = (version, style = APIPathStyle.FLAT) => `/${style}/${version}`;
|
||||
const getApiRootForVersion = (version:string, style:any = APIPathStyle.FLAT): string => `/${style}/${version}`;
|
||||
|
||||
/**
|
||||
* Helper to generate an OpenAPI server object when serving definitions
|
||||
|
@ -710,6 +721,8 @@ const getApiRootForVersion = (version, style = APIPathStyle.FLAT) => `/${style}/
|
|||
* @param {Request} req The express request object
|
||||
* @return {url: String} The server object for the OpenAPI definition location
|
||||
*/
|
||||
const generateServerForApiVersion = (apiRoot, req) => ({
|
||||
const generateServerForApiVersion = (apiRoot:string, req:any): {
|
||||
url:string
|
||||
} => ({
|
||||
url: `${settings.ssl ? 'https' : 'http'}://${req.headers.host}${apiRoot}`,
|
||||
});
|
|
@ -1,10 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
|
||||
const padManager = require('../../db/PadManager');
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
exports.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, res, next, padId) => {
|
||||
args.app.param('pad', (req:any, res:any, next:Function, padId:string) => {
|
||||
(async () => {
|
||||
// ensure the padname is valid and the url doesn't end with a /
|
||||
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
|
||||
const events = require('events');
|
||||
const express = require('../express');
|
||||
const log4js = require('log4js');
|
||||
|
@ -10,7 +12,7 @@ const socketIORouter = require('../../handler/SocketIORouter');
|
|||
const hooks = require('../../../static/js/pluginfw/hooks');
|
||||
const padMessageHandler = require('../../handler/PadMessageHandler');
|
||||
|
||||
let io;
|
||||
let io:any;
|
||||
const logger = log4js.getLogger('socket.io');
|
||||
const sockets = new Set();
|
||||
const socketsEvents = new events.EventEmitter();
|
||||
|
@ -46,7 +48,7 @@ exports.expressCloseServer = async () => {
|
|||
logger.info('All socket.io clients have disconnected');
|
||||
};
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||
// init socket.io and redirect all requests to the MessageHandler
|
||||
// there shouldn't be a browser that isn't compatible to all
|
||||
// transports in this list at once
|
||||
|
@ -77,7 +79,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
maxHttpBufferSize: settings.socketIo.maxHttpBufferSize,
|
||||
});
|
||||
|
||||
io.on('connect', (socket) => {
|
||||
io.on('connect', (socket:any) => {
|
||||
sockets.add(socket);
|
||||
socketsEvents.emit('updated');
|
||||
socket.on('disconnect', () => {
|
||||
|
@ -86,7 +88,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
});
|
||||
});
|
||||
|
||||
io.use((socket, next) => {
|
||||
io.use((socket:any, next: Function) => {
|
||||
const req = socket.request;
|
||||
// Express sets req.ip but socket.io does not. Replicate Express's behavior here.
|
||||
if (req.ip == null) {
|
||||
|
@ -105,8 +107,8 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
express.sessionMiddleware(req, {}, next);
|
||||
});
|
||||
|
||||
io.use((socket, next) => {
|
||||
socket.conn.on('packet', (packet) => {
|
||||
io.use((socket:any, next:Function) => {
|
||||
socket.conn.on('packet', (packet:string) => {
|
||||
// Tell express-session that the session is still active. The session store can use these
|
||||
// touch events to defer automatic session cleanup, and if express-session is configured with
|
||||
// rolling=true the cookie's expiration time will be renewed. (Note that WebSockets does not
|
|
@ -1,5 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
import type {MapArrayType} from "../types/MapType";
|
||||
import {I18nPluginDefs} from "../types/I18nPluginDefs";
|
||||
|
||||
const languages = require('languages4translatewiki');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
@ -11,17 +14,17 @@ const settings = require('../utils/Settings');
|
|||
// returns all existing messages merged together and grouped by langcode
|
||||
// {es: {"foo": "string"}, en:...}
|
||||
const getAllLocales = () => {
|
||||
const locales2paths = {};
|
||||
const locales2paths:MapArrayType<string[]> = {};
|
||||
|
||||
// Puts the paths of all locale files contained in a given directory
|
||||
// into `locales2paths` (files from various dirs are grouped by lang code)
|
||||
// (only json files with valid language code as name)
|
||||
const extractLangs = (dir) => {
|
||||
const extractLangs = (dir: string) => {
|
||||
if (!existsSync(dir)) return;
|
||||
let stat = fs.lstatSync(dir);
|
||||
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
||||
fs.readdirSync(dir).forEach((file) => {
|
||||
fs.readdirSync(dir).forEach((file:string) => {
|
||||
file = path.resolve(dir, file);
|
||||
stat = fs.lstatSync(file);
|
||||
if (stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
@ -40,15 +43,15 @@ const getAllLocales = () => {
|
|||
extractLangs(path.join(settings.root, 'src/locales'));
|
||||
|
||||
// add plugins languages (if any)
|
||||
for (const {package: {path: pluginPath}} of Object.values(pluginDefs.plugins)) {
|
||||
for (const {package: {path: pluginPath}} of Object.values<I18nPluginDefs>(pluginDefs.plugins)) {
|
||||
// plugin locales should overwrite etherpad's core locales
|
||||
if (pluginPath.endsWith('/ep_etherpad-lite') === true) continue;
|
||||
if (pluginPath.endsWith('/ep_etherpad-lite')) continue;
|
||||
extractLangs(path.join(pluginPath, 'locales'));
|
||||
}
|
||||
|
||||
// Build a locale index (merge all locale data other than user-supplied overrides)
|
||||
const locales = {};
|
||||
_.each(locales2paths, (files, langcode) => {
|
||||
const locales:MapArrayType<any> = {};
|
||||
_.each(locales2paths, (files: string[], langcode: string) => {
|
||||
locales[langcode] = {};
|
||||
|
||||
files.forEach((file) => {
|
||||
|
@ -70,9 +73,9 @@ const getAllLocales = () => {
|
|||
'for Customization for Administrators, under Localization.');
|
||||
if (settings.customLocaleStrings) {
|
||||
if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
|
||||
_.each(settings.customLocaleStrings, (overrides, langcode) => {
|
||||
_.each(settings.customLocaleStrings, (overrides:MapArrayType<string> , langcode:string) => {
|
||||
if (typeof overrides !== 'object') throw wrongFormatErr;
|
||||
_.each(overrides, (localeString, key) => {
|
||||
_.each(overrides, (localeString:string|object, key:string) => {
|
||||
if (typeof localeString !== 'string') throw wrongFormatErr;
|
||||
const locale = locales[langcode];
|
||||
|
||||
|
@ -102,8 +105,8 @@ const getAllLocales = () => {
|
|||
|
||||
// returns a hash of all available languages availables with nativeName and direction
|
||||
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
|
||||
const getAvailableLangs = (locales) => {
|
||||
const result = {};
|
||||
const getAvailableLangs = (locales:MapArrayType<any>) => {
|
||||
const result:MapArrayType<string> = {};
|
||||
for (const langcode of Object.keys(locales)) {
|
||||
result[langcode] = languages.getLanguageInfo(langcode);
|
||||
}
|
||||
|
@ -111,7 +114,7 @@ const getAvailableLangs = (locales) => {
|
|||
};
|
||||
|
||||
// returns locale index that will be served in /locales.json
|
||||
const generateLocaleIndex = (locales) => {
|
||||
const generateLocaleIndex = (locales:MapArrayType<string>) => {
|
||||
const result = _.clone(locales); // keep English strings
|
||||
for (const langcode of Object.keys(locales)) {
|
||||
if (langcode !== 'en') result[langcode] = `locales/${langcode}.json`;
|
||||
|
@ -120,13 +123,13 @@ const generateLocaleIndex = (locales) => {
|
|||
};
|
||||
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
// regenerate locales on server restart
|
||||
const locales = getAllLocales();
|
||||
const localeIndex = generateLocaleIndex(locales);
|
||||
exports.availableLangs = getAvailableLangs(locales);
|
||||
|
||||
app.get('/locales/:locale', (req, res) => {
|
||||
app.get('/locales/:locale', (req:any, res:any) => {
|
||||
// works with /locale/en and /locale/en.json requests
|
||||
const locale = req.params.locale.split('.')[0];
|
||||
if (Object.prototype.hasOwnProperty.call(exports.availableLangs, locale)) {
|
||||
|
@ -138,7 +141,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.get('/locales.json', (req, res) => {
|
||||
app.get('/locales.json', (req: any, res:any) => {
|
||||
res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send(localeIndex);
|
Loading…
Add table
Add a link
Reference in a new issue