mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-21 16:06:16 -04:00
Added backend in typescript. (#6185)
This commit is contained in:
parent
c2b9df3b24
commit
295a2a758b
18 changed files with 211 additions and 118 deletions
|
@ -105,7 +105,7 @@ const padList = new class {
|
||||||
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
|
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
|
||||||
* applicable).
|
* applicable).
|
||||||
*/
|
*/
|
||||||
exports.getPad = async (id: string, text: string, authorId:string = '') => {
|
exports.getPad = async (id: string, text: string|null, authorId:string = '') => {
|
||||||
// check if this is a valid padId
|
// check if this is a valid padId
|
||||||
if (!exports.isValidPadId(id)) {
|
if (!exports.isValidPadId(id)) {
|
||||||
throw new CustomError(`${id} is not a valid padId`, 'apierror');
|
throw new CustomError(`${id} is not a valid padId`, 'apierror');
|
||||||
|
|
|
@ -19,19 +19,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {MapArrayType} from "../types/MapType";
|
||||||
|
|
||||||
const absolutePaths = require('../utils/AbsolutePaths');
|
const absolutePaths = require('../utils/AbsolutePaths');
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const api = require('../db/API');
|
const api = require('../db/API');
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const randomString = require('../utils/randomstring');
|
const randomString = require('../utils/randomstring');
|
||||||
const argv = require('../utils/Cli').argv;
|
const argv = require('../utils/Cli').argv;
|
||||||
const createHTTPError = require('http-errors');
|
import createHTTPError from 'http-errors';
|
||||||
|
|
||||||
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||||
|
|
||||||
// ensure we have an apikey
|
// ensure we have an apikey
|
||||||
let apikey = null;
|
let apikey:string|null = null;
|
||||||
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -41,11 +43,11 @@ try {
|
||||||
apiHandlerLogger.info(
|
apiHandlerLogger.info(
|
||||||
`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
|
`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
|
||||||
apikey = randomString(32);
|
apikey = randomString(32);
|
||||||
fs.writeFileSync(apikeyFilename, apikey, 'utf8');
|
fs.writeFileSync(apikeyFilename, apikey!, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list of all functions
|
// a list of all functions
|
||||||
const version = {};
|
const version:MapArrayType<any> = {};
|
||||||
|
|
||||||
version['1'] = {
|
version['1'] = {
|
||||||
createGroup: [],
|
createGroup: [],
|
||||||
|
@ -163,6 +165,14 @@ exports.latestApiVersion = '1.3.0';
|
||||||
// exports the versions so it can be used by the new Swagger endpoint
|
// exports the versions so it can be used by the new Swagger endpoint
|
||||||
exports.version = version;
|
exports.version = version;
|
||||||
|
|
||||||
|
|
||||||
|
type APIFields = {
|
||||||
|
apikey: string;
|
||||||
|
api_key: string;
|
||||||
|
padID: string;
|
||||||
|
padName: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a HTTP API call
|
* Handles a HTTP API call
|
||||||
* @param {String} apiVersion the version of the api
|
* @param {String} apiVersion the version of the api
|
||||||
|
@ -171,7 +181,7 @@ exports.version = version;
|
||||||
* @req express request object
|
* @req express request object
|
||||||
* @res express response object
|
* @res express response object
|
||||||
*/
|
*/
|
||||||
exports.handle = async function (apiVersion, functionName, fields) {
|
exports.handle = async function (apiVersion: string, functionName: string, fields: APIFields) {
|
||||||
// say goodbye if this is an unknown API version
|
// say goodbye if this is an unknown API version
|
||||||
if (!(apiVersion in version)) {
|
if (!(apiVersion in version)) {
|
||||||
throw new createHTTPError.NotFound('no such api version');
|
throw new createHTTPError.NotFound('no such api version');
|
||||||
|
@ -185,7 +195,7 @@ exports.handle = async function (apiVersion, functionName, fields) {
|
||||||
// check the api key!
|
// check the api key!
|
||||||
fields.apikey = fields.apikey || fields.api_key;
|
fields.apikey = fields.apikey || fields.api_key;
|
||||||
|
|
||||||
if (fields.apikey !== apikey.trim()) {
|
if (fields.apikey !== apikey!.trim()) {
|
||||||
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +211,7 @@ exports.handle = async function (apiVersion, functionName, fields) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the function parameters in an array
|
// put the function parameters in an array
|
||||||
|
// @ts-ignore
|
||||||
const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
|
const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
|
||||||
|
|
||||||
// call the api function
|
// call the api function
|
|
@ -23,11 +23,11 @@
|
||||||
const exporthtml = require('../utils/ExportHtml');
|
const exporthtml = require('../utils/ExportHtml');
|
||||||
const exporttxt = require('../utils/ExportTxt');
|
const exporttxt = require('../utils/ExportTxt');
|
||||||
const exportEtherpad = require('../utils/ExportEtherpad');
|
const exportEtherpad = require('../utils/ExportEtherpad');
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
const os = require('os');
|
import os from 'os';
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
const util = require('util');
|
import util from 'util';
|
||||||
const { checkValidRev } = require('../utils/checkValidRev');
|
const { checkValidRev } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
const fsp_writeFile = util.promisify(fs.writeFile);
|
const fsp_writeFile = util.promisify(fs.writeFile);
|
||||||
|
@ -43,7 +43,7 @@ const tempDirectory = os.tmpdir();
|
||||||
* @param {String} readOnlyId the read only id of the pad to export
|
* @param {String} readOnlyId the read only id of the pad to export
|
||||||
* @param {String} type the type to export
|
* @param {String} type the type to export
|
||||||
*/
|
*/
|
||||||
exports.doExport = async (req, res, padId, readOnlyId, type) => {
|
exports.doExport = async (req: any, res: any, padId: string, readOnlyId: string, type:string) => {
|
||||||
// avoid naming the read-only file as the original pad's id
|
// avoid naming the read-only file as the original pad's id
|
||||||
let fileName = readOnlyId ? readOnlyId : padId;
|
let fileName = readOnlyId ? readOnlyId : padId;
|
||||||
|
|
|
@ -23,21 +23,22 @@
|
||||||
|
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const padMessageHandler = require('./PadMessageHandler');
|
const padMessageHandler = require('./PadMessageHandler');
|
||||||
const fs = require('fs').promises;
|
import {promises as fs} from 'fs';
|
||||||
const path = require('path');
|
import path from 'path';
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
const {Formidable} = require('formidable');
|
const {Formidable} = require('formidable');
|
||||||
const os = require('os');
|
import os from 'os';
|
||||||
const importHtml = require('../utils/ImportHtml');
|
const importHtml = require('../utils/ImportHtml');
|
||||||
const importEtherpad = require('../utils/ImportEtherpad');
|
const importEtherpad = require('../utils/ImportEtherpad');
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
|
|
||||||
const logger = log4js.getLogger('ImportHandler');
|
const logger = log4js.getLogger('ImportHandler');
|
||||||
|
|
||||||
// `status` must be a string supported by `importErrorMessage()` in `src/static/js/pad_impexp.js`.
|
// `status` must be a string supported by `importErrorMessage()` in `src/static/js/pad_impexp.js`.
|
||||||
class ImportError extends Error {
|
class ImportError extends Error {
|
||||||
constructor(status, ...args) {
|
status: string;
|
||||||
|
constructor(status: string, ...args:any) {
|
||||||
super(...args);
|
super(...args);
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(this, ImportError);
|
if (Error.captureStackTrace) Error.captureStackTrace(this, ImportError);
|
||||||
this.name = 'ImportError';
|
this.name = 'ImportError';
|
||||||
|
@ -47,15 +48,15 @@ class ImportError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rm = async (path) => {
|
const rm = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
await fs.unlink(path);
|
await fs.unlink(path);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
if (err.code !== 'ENOENT') throw err;
|
if (err.code !== 'ENOENT') throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let converter = null;
|
let converter:any = null;
|
||||||
let exportExtension = 'htm';
|
let exportExtension = 'htm';
|
||||||
|
|
||||||
// load abiword only if it is enabled and if soffice is disabled
|
// load abiword only if it is enabled and if soffice is disabled
|
||||||
|
@ -78,7 +79,7 @@ const tmpDirectory = os.tmpdir();
|
||||||
* @param {String} padId the pad id to export
|
* @param {String} padId the pad id to export
|
||||||
* @param {String} authorId the author id to use for the import
|
* @param {String} authorId the author id to use for the import
|
||||||
*/
|
*/
|
||||||
const doImport = async (req, res, padId, authorId) => {
|
const doImport = async (req:any, res:any, padId:string, authorId:string) => {
|
||||||
// pipe to a file
|
// pipe to a file
|
||||||
// convert file to html via abiword or soffice
|
// convert file to html via abiword or soffice
|
||||||
// set html in the pad
|
// set html in the pad
|
||||||
|
@ -98,7 +99,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
let fields;
|
let fields;
|
||||||
try {
|
try {
|
||||||
[fields, files] = await form.parse(req);
|
[fields, files] = await form.parse(req);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
logger.warn(`Import failed due to form error: ${err.stack || err}`);
|
logger.warn(`Import failed due to form error: ${err.stack || err}`);
|
||||||
if (err.code === Formidable.formidableErrors.biggerThanMaxFileSize) {
|
if (err.code === Formidable.formidableErrors.biggerThanMaxFileSize) {
|
||||||
throw new ImportError('maxFileSize');
|
throw new ImportError('maxFileSize');
|
||||||
|
@ -136,7 +137,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
|
|
||||||
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
|
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
|
||||||
const context = {srcFile, destFile, fileEnding, padId, ImportError};
|
const context = {srcFile, destFile, fileEnding, padId, ImportError};
|
||||||
const importHandledByPlugin = (await hooks.aCallAll('import', context)).some((x) => x);
|
const importHandledByPlugin = (await hooks.aCallAll('import', context)).some((x:string) => x);
|
||||||
const fileIsEtherpad = (fileEnding === '.etherpad');
|
const fileIsEtherpad = (fileEnding === '.etherpad');
|
||||||
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
|
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
|
||||||
const fileIsTXT = (fileEnding === '.txt');
|
const fileIsTXT = (fileEnding === '.txt');
|
||||||
|
@ -169,7 +170,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await converter.convertFile(srcFile, destFile, exportExtension);
|
await converter.convertFile(srcFile, destFile, exportExtension);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
logger.warn(`Converting Error: ${err.stack || err}`);
|
logger.warn(`Converting Error: ${err.stack || err}`);
|
||||||
throw new ImportError('convertFailed');
|
throw new ImportError('convertFailed');
|
||||||
}
|
}
|
||||||
|
@ -210,7 +211,7 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
if (importHandledByPlugin || useConverter || fileIsHTML) {
|
if (importHandledByPlugin || useConverter || fileIsHTML) {
|
||||||
try {
|
try {
|
||||||
await importHtml.setPadHTML(pad, text, authorId);
|
await importHtml.setPadHTML(pad, text, authorId);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`);
|
logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -245,14 +246,14 @@ const doImport = async (req, res, padId, authorId) => {
|
||||||
* @param {String} authorId the author id to use for the import
|
* @param {String} authorId the author id to use for the import
|
||||||
* @return {Promise<void>} a promise
|
* @return {Promise<void>} a promise
|
||||||
*/
|
*/
|
||||||
exports.doImport = async (req, res, padId, authorId = '') => {
|
exports.doImport = async (req:any, res:any, padId:string, authorId:string = '') => {
|
||||||
let httpStatus = 200;
|
let httpStatus = 200;
|
||||||
let code = 0;
|
let code = 0;
|
||||||
let message = 'ok';
|
let message = 'ok';
|
||||||
let directDatabaseAccess;
|
let directDatabaseAccess;
|
||||||
try {
|
try {
|
||||||
directDatabaseAccess = await doImport(req, res, padId, authorId);
|
directDatabaseAccess = await doImport(req, res, padId, authorId);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
const known = err instanceof ImportError && err.status;
|
const known = err instanceof ImportError && err.status;
|
||||||
if (!known) logger.error(`Internal error during import: ${err.stack || err}`);
|
if (!known) logger.error(`Internal error during import: ${err.stack || err}`);
|
||||||
httpStatus = known ? 400 : 500;
|
httpStatus = known ? 400 : 500;
|
|
@ -19,6 +19,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {MapArrayType} from "../types/MapType";
|
||||||
|
|
||||||
const AttributeMap = require('../../static/js/AttributeMap');
|
const AttributeMap = require('../../static/js/AttributeMap');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const Changeset = require('../../static/js/Changeset');
|
const Changeset = require('../../static/js/Changeset');
|
||||||
|
@ -37,16 +39,19 @@ const accessLogger = log4js.getLogger('access');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks.js');
|
const hooks = require('../../static/js/pluginfw/hooks.js');
|
||||||
const stats = require('../stats')
|
const stats = require('../stats')
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const {RateLimiterMemory} = require('rate-limiter-flexible');
|
import {RateLimiterMemory} from 'rate-limiter-flexible';
|
||||||
|
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
|
||||||
|
import {APool, AText, PadAuthor, PadType} from "../types/PadType";
|
||||||
|
import {ChangeSet} from "../types/ChangeSet";
|
||||||
const webaccess = require('../hooks/express/webaccess');
|
const webaccess = require('../hooks/express/webaccess');
|
||||||
const { checkValidRev } = require('../utils/checkValidRev');
|
const { checkValidRev } = require('../utils/checkValidRev');
|
||||||
|
|
||||||
let rateLimiter;
|
let rateLimiter:any;
|
||||||
let socketio = null;
|
let socketio:any = null;
|
||||||
|
|
||||||
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
|
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
|
||||||
|
|
||||||
const addContextToError = (err, pfx) => {
|
const addContextToError = (err:any, pfx:string) => {
|
||||||
const newErr = new Error(`${pfx}${err.message}`, {cause: err});
|
const newErr = new Error(`${pfx}${err.message}`, {cause: err});
|
||||||
if (Error.captureStackTrace) Error.captureStackTrace(newErr, addContextToError);
|
if (Error.captureStackTrace) Error.captureStackTrace(newErr, addContextToError);
|
||||||
// Check for https://github.com/tc39/proposal-error-cause support, available in Node.js >= v16.10.
|
// Check for https://github.com/tc39/proposal-error-cause support, available in Node.js >= v16.10.
|
||||||
|
@ -80,7 +85,7 @@ exports.socketio = () => {
|
||||||
* - readonly: Whether the client has read-only access (true) or read/write access (false).
|
* - readonly: Whether the client has read-only access (true) or read/write access (false).
|
||||||
* - rev: The last revision that was sent to the client.
|
* - rev: The last revision that was sent to the client.
|
||||||
*/
|
*/
|
||||||
const sessioninfos = {};
|
const sessioninfos:MapArrayType<any> = {};
|
||||||
exports.sessioninfos = sessioninfos;
|
exports.sessioninfos = sessioninfos;
|
||||||
|
|
||||||
stats.gauge('totalUsers', () => socketio ? socketio.sockets.size : 0);
|
stats.gauge('totalUsers', () => socketio ? socketio.sockets.size : 0);
|
||||||
|
@ -97,11 +102,13 @@ stats.gauge('activePads', () => {
|
||||||
* Processes one task at a time per channel.
|
* Processes one task at a time per channel.
|
||||||
*/
|
*/
|
||||||
class Channels {
|
class Channels {
|
||||||
|
private readonly _exec: (ch:any, task:any) => any;
|
||||||
|
private _promiseChains: Map<any, Promise<any>>;
|
||||||
/**
|
/**
|
||||||
* @param {(ch, task) => any} [exec] - Task executor. If omitted, tasks are assumed to be
|
* @param {(ch, task) => any} [exec] - Task executor. If omitted, tasks are assumed to be
|
||||||
* functions that will be executed with the channel as the only argument.
|
* functions that will be executed with the channel as the only argument.
|
||||||
*/
|
*/
|
||||||
constructor(exec = (ch, task) => task(ch)) {
|
constructor(exec = (ch: string, task:any) => task(ch)) {
|
||||||
this._exec = exec;
|
this._exec = exec;
|
||||||
this._promiseChains = new Map();
|
this._promiseChains = new Map();
|
||||||
}
|
}
|
||||||
|
@ -114,7 +121,7 @@ class Channels {
|
||||||
* @param {any} task - The task to give to the executor.
|
* @param {any} task - The task to give to the executor.
|
||||||
* @returns {Promise<any>} The value returned by the executor.
|
* @returns {Promise<any>} The value returned by the executor.
|
||||||
*/
|
*/
|
||||||
async enqueue(ch, task) {
|
async enqueue(ch:any, task:any): Promise<any> {
|
||||||
const p = (this._promiseChains.get(ch) || Promise.resolve()).then(() => this._exec(ch, task));
|
const p = (this._promiseChains.get(ch) || Promise.resolve()).then(() => this._exec(ch, task));
|
||||||
const pc = p
|
const pc = p
|
||||||
.catch(() => {}) // Prevent rejections from halting the queue.
|
.catch(() => {}) // Prevent rejections from halting the queue.
|
||||||
|
@ -136,7 +143,7 @@ const padChannels = new Channels((ch, {socket, message}) => handleUserChanges(so
|
||||||
* This Method is called by server.ts to tell the message handler on which socket it should send
|
* This Method is called by server.ts to tell the message handler on which socket it should send
|
||||||
* @param socket_io The Socket
|
* @param socket_io The Socket
|
||||||
*/
|
*/
|
||||||
exports.setSocketIO = (socket_io) => {
|
exports.setSocketIO = (socket_io:any) => {
|
||||||
socketio = socket_io;
|
socketio = socket_io;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,7 +151,7 @@ exports.setSocketIO = (socket_io) => {
|
||||||
* Handles the connection of a new user
|
* Handles the connection of a new user
|
||||||
* @param socket the socket.io Socket object for the new connection from the client
|
* @param socket the socket.io Socket object for the new connection from the client
|
||||||
*/
|
*/
|
||||||
exports.handleConnect = (socket) => {
|
exports.handleConnect = (socket:any) => {
|
||||||
stats.meter('connects').mark();
|
stats.meter('connects').mark();
|
||||||
|
|
||||||
// Initialize sessioninfos for this new session
|
// Initialize sessioninfos for this new session
|
||||||
|
@ -154,7 +161,7 @@ exports.handleConnect = (socket) => {
|
||||||
/**
|
/**
|
||||||
* Kicks all sessions from a pad
|
* Kicks all sessions from a pad
|
||||||
*/
|
*/
|
||||||
exports.kickSessionsFromPad = (padID) => {
|
exports.kickSessionsFromPad = (padID: string) => {
|
||||||
if (typeof socketio.sockets.clients !== 'object') return;
|
if (typeof socketio.sockets.clients !== 'object') return;
|
||||||
|
|
||||||
// skip if there is nobody on this pad
|
// skip if there is nobody on this pad
|
||||||
|
@ -168,13 +175,13 @@ exports.kickSessionsFromPad = (padID) => {
|
||||||
* Handles the disconnection of a user
|
* Handles the disconnection of a user
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
*/
|
*/
|
||||||
exports.handleDisconnect = async (socket) => {
|
exports.handleDisconnect = async (socket:any) => {
|
||||||
stats.meter('disconnects').mark();
|
stats.meter('disconnects').mark();
|
||||||
const session = sessioninfos[socket.id];
|
const session = sessioninfos[socket.id];
|
||||||
delete sessioninfos[socket.id];
|
delete sessioninfos[socket.id];
|
||||||
// session.padId can be nullish if the user disconnects before sending CLIENT_READY.
|
// session.padId can be nullish if the user disconnects before sending CLIENT_READY.
|
||||||
if (!session || !session.author || !session.padId) return;
|
if (!session || !session.author || !session.padId) return;
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}} = socket.client.request as SocketClientRequest;
|
||||||
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
||||||
accessLogger.info('[LEAVE]' +
|
accessLogger.info('[LEAVE]' +
|
||||||
` pad:${session.padId}` +
|
` pad:${session.padId}` +
|
||||||
|
@ -206,7 +213,7 @@ exports.handleDisconnect = async (socket) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
exports.handleMessage = async (socket, message) => {
|
exports.handleMessage = async (socket:any, message:typeof ChatMessage) => {
|
||||||
const env = process.env.NODE_ENV || 'development';
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
if (env === 'production') {
|
if (env === 'production') {
|
||||||
|
@ -263,7 +270,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
|
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}} = socket.client.request as SocketClientRequest;
|
||||||
const {accessStatus, authorID} =
|
const {accessStatus, authorID} =
|
||||||
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
|
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
|
||||||
if (accessStatus !== 'grant') {
|
if (accessStatus !== 'grant') {
|
||||||
|
@ -319,7 +326,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call handleMessage hook. If a plugin returns null, the message will be dropped.
|
// Call handleMessage hook. If a plugin returns null, the message will be dropped.
|
||||||
if ((await hooks.aCallAll('handleMessage', context)).some((m) => m == null)) {
|
if ((await hooks.aCallAll('handleMessage', context)).some((m: null|string) => m == null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +383,7 @@ exports.handleMessage = async (socket, message) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleSaveRevisionMessage = async (socket, message) => {
|
const handleSaveRevisionMessage = async (socket:any, message: string) => {
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
const pad = await padManager.getPad(padId, null, authorId);
|
const pad = await padManager.getPad(padId, null, authorId);
|
||||||
await pad.addSavedRevision(pad.head, authorId);
|
await pad.addSavedRevision(pad.head, authorId);
|
||||||
|
@ -389,7 +396,7 @@ const handleSaveRevisionMessage = async (socket, message) => {
|
||||||
* @param msg {Object} the message we're sending
|
* @param msg {Object} the message we're sending
|
||||||
* @param sessionID {string} the socketIO session to which we're sending this message
|
* @param sessionID {string} the socketIO session to which we're sending this message
|
||||||
*/
|
*/
|
||||||
exports.handleCustomObjectMessage = (msg, sessionID) => {
|
exports.handleCustomObjectMessage = (msg: typeof ChatMessage, sessionID: string) => {
|
||||||
if (msg.data.type === 'CUSTOM') {
|
if (msg.data.type === 'CUSTOM') {
|
||||||
if (sessionID) {
|
if (sessionID) {
|
||||||
// a sessionID is targeted: directly to this sessionID
|
// a sessionID is targeted: directly to this sessionID
|
||||||
|
@ -407,7 +414,7 @@ exports.handleCustomObjectMessage = (msg, sessionID) => {
|
||||||
* @param padID {Pad} the pad to which we're sending this message
|
* @param padID {Pad} the pad to which we're sending this message
|
||||||
* @param msgString {String} the message we're sending
|
* @param msgString {String} the message we're sending
|
||||||
*/
|
*/
|
||||||
exports.handleCustomMessage = (padID, msgString) => {
|
exports.handleCustomMessage = (padID: string, msgString:string) => {
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
const msg = {
|
const msg = {
|
||||||
type: 'COLLABROOM',
|
type: 'COLLABROOM',
|
||||||
|
@ -424,7 +431,7 @@ exports.handleCustomMessage = (padID, msgString) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleChatMessage = async (socket, message) => {
|
const handleChatMessage = async (socket:any, message: typeof ChatMessage) => {
|
||||||
const chatMessage = ChatMessage.fromObject(message.data.message);
|
const chatMessage = ChatMessage.fromObject(message.data.message);
|
||||||
const {padId, author: authorId} = sessioninfos[socket.id];
|
const {padId, author: authorId} = sessioninfos[socket.id];
|
||||||
// Don't trust the user-supplied values.
|
// Don't trust the user-supplied values.
|
||||||
|
@ -444,7 +451,7 @@ const handleChatMessage = async (socket, message) => {
|
||||||
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
* @param {string} [padId] - The destination pad ID. Deprecated; pass a chat message
|
||||||
* object as the first argument and the destination pad ID as the second argument instead.
|
* object as the first argument and the destination pad ID as the second argument instead.
|
||||||
*/
|
*/
|
||||||
exports.sendChatMessageToPadClients = async (mt, puId, text = null, padId = null) => {
|
exports.sendChatMessageToPadClients = async (mt: typeof ChatMessage|number, puId: string, text:string|null = null, padId:string|null = null) => {
|
||||||
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
||||||
padId = mt instanceof ChatMessage ? puId : padId;
|
padId = mt instanceof ChatMessage ? puId : padId;
|
||||||
const pad = await padManager.getPad(padId, null, message.authorId);
|
const pad = await padManager.getPad(padId, null, message.authorId);
|
||||||
|
@ -465,7 +472,7 @@ exports.sendChatMessageToPadClients = async (mt, puId, text = null, padId = null
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleGetChatMessages = async (socket, {data: {start, end}}) => {
|
const handleGetChatMessages = async (socket:any, {data: {start, end}}:any) => {
|
||||||
if (!Number.isInteger(start)) throw new Error(`missing or invalid start: ${start}`);
|
if (!Number.isInteger(start)) throw new Error(`missing or invalid start: ${start}`);
|
||||||
if (!Number.isInteger(end)) throw new Error(`missing or invalid end: ${end}`);
|
if (!Number.isInteger(end)) throw new Error(`missing or invalid end: ${end}`);
|
||||||
const count = end - start;
|
const count = end - start;
|
||||||
|
@ -491,7 +498,7 @@ const handleGetChatMessages = async (socket, {data: {start, end}}) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleSuggestUserName = (socket, message) => {
|
const handleSuggestUserName = (socket:any, message: typeof ChatMessage) => {
|
||||||
const {newName, unnamedId} = message.data.payload;
|
const {newName, unnamedId} = message.data.payload;
|
||||||
if (newName == null) throw new Error('missing newName');
|
if (newName == null) throw new Error('missing newName');
|
||||||
if (unnamedId == null) throw new Error('missing unnamedId');
|
if (unnamedId == null) throw new Error('missing unnamedId');
|
||||||
|
@ -511,7 +518,7 @@ const handleSuggestUserName = (socket, message) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}}) => {
|
const handleUserInfoUpdate = async (socket:any, {data: {userInfo: {name, colorId}}}: PadUserInfo) => {
|
||||||
if (colorId == null) throw new Error('missing colorId');
|
if (colorId == null) throw new Error('missing colorId');
|
||||||
if (!name) name = null;
|
if (!name) name = null;
|
||||||
const session = sessioninfos[socket.id];
|
const session = sessioninfos[socket.id];
|
||||||
|
@ -559,7 +566,7 @@ const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}})
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleUserChanges = async (socket, message) => {
|
const handleUserChanges = async (socket:any, message: typeof ChatMessage) => {
|
||||||
// This one's no longer pending, as we're gonna process it now
|
// This one's no longer pending, as we're gonna process it now
|
||||||
stats.counter('pendingEdits').dec();
|
stats.counter('pendingEdits').dec();
|
||||||
|
|
||||||
|
@ -658,7 +665,7 @@ const handleUserChanges = async (socket, message) => {
|
||||||
thisSession.rev = newRev;
|
thisSession.rev = newRev;
|
||||||
if (newRev !== r) thisSession.time = await pad.getRevisionDate(newRev);
|
if (newRev !== r) thisSession.time = await pad.getRevisionDate(newRev);
|
||||||
await exports.updatePadClients(pad);
|
await exports.updatePadClients(pad);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
socket.emit('message', {disconnect: 'badChangeset'});
|
socket.emit('message', {disconnect: 'badChangeset'});
|
||||||
stats.meter('failedChangesets').mark();
|
stats.meter('failedChangesets').mark();
|
||||||
messageLogger.warn(`Failed to apply USER_CHANGES from author ${thisSession.author} ` +
|
messageLogger.warn(`Failed to apply USER_CHANGES from author ${thisSession.author} ` +
|
||||||
|
@ -668,7 +675,7 @@ const handleUserChanges = async (socket, message) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.updatePadClients = async (pad) => {
|
exports.updatePadClients = async (pad: PadType) => {
|
||||||
// skip this if no-one is on this pad
|
// skip this if no-one is on this pad
|
||||||
const roomSockets = _getRoomSockets(pad.id);
|
const roomSockets = _getRoomSockets(pad.id);
|
||||||
if (roomSockets.length === 0) return;
|
if (roomSockets.length === 0) return;
|
||||||
|
@ -683,7 +690,7 @@ exports.updatePadClients = async (pad) => {
|
||||||
// via async.forEach with sequential for() loop. There is no real
|
// via async.forEach with sequential for() loop. There is no real
|
||||||
// benefits of running this in parallel,
|
// benefits of running this in parallel,
|
||||||
// but benefit of reusing cached revision object is HUGE
|
// but benefit of reusing cached revision object is HUGE
|
||||||
const revCache = {};
|
const revCache:MapArrayType<any> = {};
|
||||||
|
|
||||||
await Promise.all(roomSockets.map(async (socket) => {
|
await Promise.all(roomSockets.map(async (socket) => {
|
||||||
const sessioninfo = sessioninfos[socket.id];
|
const sessioninfo = sessioninfos[socket.id];
|
||||||
|
@ -717,7 +724,7 @@ exports.updatePadClients = async (pad) => {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
socket.emit('message', msg);
|
socket.emit('message', msg);
|
||||||
} catch (err) {
|
} catch (err:any) {
|
||||||
messageLogger.error(`Failed to notify user of new revision: ${err.stack || err}`);
|
messageLogger.error(`Failed to notify user of new revision: ${err.stack || err}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -730,7 +737,7 @@ exports.updatePadClients = async (pad) => {
|
||||||
/**
|
/**
|
||||||
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
|
* Copied from the Etherpad Source Code. Don't know what this method does excatly...
|
||||||
*/
|
*/
|
||||||
const _correctMarkersInPad = (atext, apool) => {
|
const _correctMarkersInPad = (atext: AText, apool: APool) => {
|
||||||
const text = atext.text;
|
const text = atext.text;
|
||||||
|
|
||||||
// collect char positions of line markers (e.g. bullets) in new atext
|
// collect char positions of line markers (e.g. bullets) in new atext
|
||||||
|
@ -739,7 +746,7 @@ const _correctMarkersInPad = (atext, apool) => {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const op of Changeset.deserializeOps(atext.attribs)) {
|
for (const op of Changeset.deserializeOps(atext.attribs)) {
|
||||||
const attribs = AttributeMap.fromString(op.attribs, apool);
|
const attribs = AttributeMap.fromString(op.attribs, apool);
|
||||||
const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
|
const hasMarker = AttributeManager.lineAttributes.some((a: string) => attribs.has(a));
|
||||||
if (hasMarker) {
|
if (hasMarker) {
|
||||||
for (let i = 0; i < op.chars; i++) {
|
for (let i = 0; i < op.chars; i++) {
|
||||||
if (offset > 0 && text.charAt(offset - 1) !== '\n') {
|
if (offset > 0 && text.charAt(offset - 1) !== '\n') {
|
||||||
|
@ -777,7 +784,7 @@ const _correctMarkersInPad = (atext, apool) => {
|
||||||
* @param socket the socket.io Socket object for the client
|
* @param socket the socket.io Socket object for the client
|
||||||
* @param message the message from the client
|
* @param message the message from the client
|
||||||
*/
|
*/
|
||||||
const handleClientReady = async (socket, message) => {
|
const handleClientReady = async (socket:any, message: typeof ChatMessage) => {
|
||||||
const sessionInfo = sessioninfos[socket.id];
|
const sessionInfo = sessioninfos[socket.id];
|
||||||
if (sessionInfo == null) throw new Error('client disconnected');
|
if (sessionInfo == null) throw new Error('client disconnected');
|
||||||
assert(sessionInfo.author);
|
assert(sessionInfo.author);
|
||||||
|
@ -805,8 +812,11 @@ const handleClientReady = async (socket, message) => {
|
||||||
const currentTime = await pad.getRevisionDate(pad.getHeadRevisionNumber());
|
const currentTime = await pad.getRevisionDate(pad.getHeadRevisionNumber());
|
||||||
|
|
||||||
// get all author data out of the database (in parallel)
|
// get all author data out of the database (in parallel)
|
||||||
const historicalAuthorData = {};
|
const historicalAuthorData:MapArrayType<{
|
||||||
await Promise.all(authors.map(async (authorId) => {
|
name: string;
|
||||||
|
colorId: string;
|
||||||
|
}> = {};
|
||||||
|
await Promise.all(authors.map(async (authorId: string) => {
|
||||||
const author = await authorManager.getAuthor(authorId);
|
const author = await authorManager.getAuthor(authorId);
|
||||||
if (!author) {
|
if (!author) {
|
||||||
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
|
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
|
||||||
|
@ -837,7 +847,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {session: {user} = {}} = socket.client.request;
|
const {session: {user} = {}} = socket.client.request as SocketClientRequest;
|
||||||
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
|
||||||
accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` +
|
accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` +
|
||||||
` pad:${sessionInfo.padId}` +
|
` pad:${sessionInfo.padId}` +
|
||||||
|
@ -859,7 +869,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
// By using client revision,
|
// By using client revision,
|
||||||
// this below code sends all the revisions missed during the client reconnect
|
// this below code sends all the revisions missed during the client reconnect
|
||||||
const revisionsNeeded = [];
|
const revisionsNeeded = [];
|
||||||
const changesets = {};
|
const changesets:MapArrayType<any> = {};
|
||||||
|
|
||||||
let startNum = message.client_rev + 1;
|
let startNum = message.client_rev + 1;
|
||||||
let endNum = pad.getHeadRevisionNumber() + 1;
|
let endNum = pad.getHeadRevisionNumber() + 1;
|
||||||
|
@ -919,7 +929,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||||
apool = attribsForWire.pool.toJsonable();
|
apool = attribsForWire.pool.toJsonable();
|
||||||
atext.attribs = attribsForWire.translated;
|
atext.attribs = attribsForWire.translated;
|
||||||
} catch (e) {
|
} catch (e:any) {
|
||||||
messageLogger.error(e.stack || e);
|
messageLogger.error(e.stack || e);
|
||||||
socket.emit('message', {disconnect: 'corruptPad'}); // pull the brakes
|
socket.emit('message', {disconnect: 'corruptPad'}); // pull the brakes
|
||||||
throw new Error('corrupt pad');
|
throw new Error('corrupt pad');
|
||||||
|
@ -927,7 +937,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
|
|
||||||
// Warning: never ever send sessionInfo.padId to the client. If the client is read only you
|
// Warning: never ever send sessionInfo.padId to the client. If the client is read only you
|
||||||
// would open a security hole 1 swedish mile wide...
|
// would open a security hole 1 swedish mile wide...
|
||||||
const clientVars = {
|
const clientVars:MapArrayType<any> = {
|
||||||
skinName: settings.skinName,
|
skinName: settings.skinName,
|
||||||
skinVariants: settings.skinVariants,
|
skinVariants: settings.skinVariants,
|
||||||
randomVersionString: settings.randomVersionString,
|
randomVersionString: settings.randomVersionString,
|
||||||
|
@ -1078,7 +1088,7 @@ const handleClientReady = async (socket, message) => {
|
||||||
/**
|
/**
|
||||||
* Handles a request for a rough changeset, the timeslider client needs it
|
* Handles a request for a rough changeset, the timeslider client needs it
|
||||||
*/
|
*/
|
||||||
const handleChangesetRequest = async (socket, {data: {granularity, start, requestID}}) => {
|
const handleChangesetRequest = async (socket:any, {data: {granularity, start, requestID}}: ChangesetRequest) => {
|
||||||
if (granularity == null) throw new Error('missing granularity');
|
if (granularity == null) throw new Error('missing granularity');
|
||||||
if (!Number.isInteger(granularity)) throw new Error('granularity is not an integer');
|
if (!Number.isInteger(granularity)) throw new Error('granularity is not an integer');
|
||||||
if (start == null) throw new Error('missing start');
|
if (start == null) throw new Error('missing start');
|
||||||
|
@ -1090,7 +1100,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
|
||||||
const headRev = pad.getHeadRevisionNumber();
|
const headRev = pad.getHeadRevisionNumber();
|
||||||
if (start > headRev)
|
if (start > headRev)
|
||||||
start = headRev;
|
start = headRev;
|
||||||
const data = await getChangesetInfo(pad, start, end, granularity);
|
const data:MapArrayType<any> = await getChangesetInfo(pad, start, end, granularity);
|
||||||
data.requestID = requestID;
|
data.requestID = requestID;
|
||||||
socket.emit('message', {type: 'CHANGESET_REQ', data});
|
socket.emit('message', {type: 'CHANGESET_REQ', data});
|
||||||
};
|
};
|
||||||
|
@ -1099,7 +1109,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
|
||||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||||
*/
|
*/
|
||||||
const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, granularity: number) => {
|
||||||
const headRevision = pad.getHeadRevisionNumber();
|
const headRevision = pad.getHeadRevisionNumber();
|
||||||
|
|
||||||
// calculate the last full endnum
|
// calculate the last full endnum
|
||||||
|
@ -1124,8 +1134,8 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all needed db values in parallel.
|
// Get all needed db values in parallel.
|
||||||
const composedChangesets = {};
|
const composedChangesets:MapArrayType<any> = {};
|
||||||
const revisionDate = [];
|
const revisionDate:number[] = [];
|
||||||
const [lines] = await Promise.all([
|
const [lines] = await Promise.all([
|
||||||
getPadLines(pad, startNum - 1),
|
getPadLines(pad, startNum - 1),
|
||||||
// Get all needed composite Changesets.
|
// Get all needed composite Changesets.
|
||||||
|
@ -1176,7 +1186,7 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
|
||||||
* Tries to rebuild the getPadLines function of the original Etherpad
|
* Tries to rebuild the getPadLines function of the original Etherpad
|
||||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
|
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
|
||||||
*/
|
*/
|
||||||
const getPadLines = async (pad, revNum) => {
|
const getPadLines = async (pad: PadType, revNum: number) => {
|
||||||
// get the atext
|
// get the atext
|
||||||
let atext;
|
let atext;
|
||||||
|
|
||||||
|
@ -1196,7 +1206,7 @@ const getPadLines = async (pad, revNum) => {
|
||||||
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
* Tries to rebuild the composePadChangeset function of the original Etherpad
|
||||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
|
||||||
*/
|
*/
|
||||||
const composePadChangesets = async (pad, startNum, endNum) => {
|
const composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
|
||||||
// fetch all changesets we need
|
// fetch all changesets we need
|
||||||
const headNum = pad.getHeadRevisionNumber();
|
const headNum = pad.getHeadRevisionNumber();
|
||||||
endNum = Math.min(endNum, headNum + 1);
|
endNum = Math.min(endNum, headNum + 1);
|
||||||
|
@ -1210,7 +1220,7 @@ const composePadChangesets = async (pad, startNum, endNum) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all changesets
|
// get all changesets
|
||||||
const changesets = {};
|
const changesets:MapArrayType<ChangeSet> = {};
|
||||||
await Promise.all(changesetsNeeded.map(
|
await Promise.all(changesetsNeeded.map(
|
||||||
(revNum) => pad.getRevisionChangeset(revNum)
|
(revNum) => pad.getRevisionChangeset(revNum)
|
||||||
.then((changeset) => changesets[revNum] = changeset)));
|
.then((changeset) => changesets[revNum] = changeset)));
|
||||||
|
@ -1234,13 +1244,13 @@ const composePadChangesets = async (pad, startNum, endNum) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getRoomSockets = (padID) => {
|
const _getRoomSockets = (padID: string) => {
|
||||||
const ns = socketio.sockets; // Default namespace.
|
const ns = socketio.sockets; // Default namespace.
|
||||||
// We could call adapter.clients(), but that method is unnecessarily asynchronous. Replicate what
|
// We could call adapter.clients(), but that method is unnecessarily asynchronous. Replicate what
|
||||||
// it does here, but synchronously to avoid a race condition. This code will have to change when
|
// it does here, but synchronously to avoid a race condition. This code will have to change when
|
||||||
// we update to socket.io v3.
|
// we update to socket.io v3.
|
||||||
const room = ns.adapter.rooms?.get(padID);
|
const room = ns.adapter.rooms?.get(padID);
|
||||||
|
|
||||||
if (!room) return [];
|
if (!room) return [];
|
||||||
|
|
||||||
return Array.from(room)
|
return Array.from(room)
|
||||||
|
@ -1251,15 +1261,15 @@ const _getRoomSockets = (padID) => {
|
||||||
/**
|
/**
|
||||||
* Get the number of users in a pad
|
* Get the number of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsersCount = (padID) => ({
|
exports.padUsersCount = (padID:string) => ({
|
||||||
padUsersCount: _getRoomSockets(padID).length,
|
padUsersCount: _getRoomSockets(padID).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of users in a pad
|
* Get the list of users in a pad
|
||||||
*/
|
*/
|
||||||
exports.padUsers = async (padID) => {
|
exports.padUsers = async (padID: string) => {
|
||||||
const padUsers = [];
|
const padUsers:PadAuthor[] = [];
|
||||||
|
|
||||||
// iterate over all clients (in parallel)
|
// iterate over all clients (in parallel)
|
||||||
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
|
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
|
|
@ -20,6 +20,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {MapArrayType} from "../types/MapType";
|
||||||
|
import {SocketModule} from "../types/SocketModule";
|
||||||
const log4js = require('log4js');
|
const log4js = require('log4js');
|
||||||
const settings = require('../utils/Settings');
|
const settings = require('../utils/Settings');
|
||||||
const stats = require('../../node/stats')
|
const stats = require('../../node/stats')
|
||||||
|
@ -31,15 +33,15 @@ const logger = log4js.getLogger('socket.io');
|
||||||
* key is the component name
|
* key is the component name
|
||||||
* value is the component module
|
* value is the component module
|
||||||
*/
|
*/
|
||||||
const components = {};
|
const components:MapArrayType<any> = {};
|
||||||
|
|
||||||
let io;
|
let io:any;
|
||||||
|
|
||||||
/** adds a component
|
/** adds a component
|
||||||
* @param {string} moduleName
|
* @param {string} moduleName
|
||||||
* @param {Module} module
|
* @param {Module} module
|
||||||
*/
|
*/
|
||||||
exports.addComponent = (moduleName, module) => {
|
exports.addComponent = (moduleName: string, module: SocketModule) => {
|
||||||
if (module == null) return exports.deleteComponent(moduleName);
|
if (module == null) return exports.deleteComponent(moduleName);
|
||||||
components[moduleName] = module;
|
components[moduleName] = module;
|
||||||
module.setSocketIO(io);
|
module.setSocketIO(io);
|
||||||
|
@ -49,22 +51,22 @@ exports.addComponent = (moduleName, module) => {
|
||||||
* removes a component
|
* removes a component
|
||||||
* @param {Module} moduleName
|
* @param {Module} moduleName
|
||||||
*/
|
*/
|
||||||
exports.deleteComponent = (moduleName) => { delete components[moduleName]; };
|
exports.deleteComponent = (moduleName: string) => { delete components[moduleName]; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets the socket.io and adds event functions for routing
|
* sets the socket.io and adds event functions for routing
|
||||||
* @param {Object} _io the socket.io instance
|
* @param {Object} _io the socket.io instance
|
||||||
*/
|
*/
|
||||||
exports.setSocketIO = (_io) => {
|
exports.setSocketIO = (_io:any) => {
|
||||||
io = _io;
|
io = _io;
|
||||||
|
|
||||||
io.sockets.on('connection', (socket) => {
|
io.sockets.on('connection', (socket:any) => {
|
||||||
const ip = settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
|
const ip = settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
|
||||||
logger.debug(`${socket.id} connected from IP ${ip}`);
|
logger.debug(`${socket.id} connected from IP ${ip}`);
|
||||||
|
|
||||||
// wrap the original send function to log the messages
|
// wrap the original send function to log the messages
|
||||||
socket._send = socket.send;
|
socket._send = socket.send;
|
||||||
socket.send = (message) => {
|
socket.send = (message: string) => {
|
||||||
logger.debug(`to ${socket.id}: ${JSON.stringify(message)}`);
|
logger.debug(`to ${socket.id}: ${JSON.stringify(message)}`);
|
||||||
socket._send(message);
|
socket._send(message);
|
||||||
};
|
};
|
||||||
|
@ -74,7 +76,7 @@ exports.setSocketIO = (_io) => {
|
||||||
components[i].handleConnect(socket);
|
components[i].handleConnect(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('message', (message, ack = () => {}) => (async () => {
|
socket.on('message', (message: any, ack: any = () => {}) => (async () => {
|
||||||
if (!message.component || !components[message.component]) {
|
if (!message.component || !components[message.component]) {
|
||||||
throw new Error(`unknown message component: ${message.component}`);
|
throw new Error(`unknown message component: ${message.component}`);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +90,7 @@ exports.setSocketIO = (_io) => {
|
||||||
ack({name: err.name, message: err.message}); // socket.io can't handle Error objects.
|
ack({name: err.name, message: err.message}); // socket.io can't handle Error objects.
|
||||||
}));
|
}));
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason: string) => {
|
||||||
logger.debug(`${socket.id} disconnected: ${reason}`);
|
logger.debug(`${socket.id} disconnected: ${reason}`);
|
||||||
// store the lastDisconnect as a timestamp, this is useful if you want to know
|
// store the lastDisconnect as a timestamp, this is useful if you want to know
|
||||||
// when the last user disconnected. If your activePads is 0 and totalUsers is 0
|
// when the last user disconnected. If your activePads is 0 and totalUsers is 0
|
|
@ -1,7 +1,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const assert = require('assert').strict;
|
import {strict as assert} from "assert";
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
|
import {SocketClientRequest} from "../../types/SocketClientRequest";
|
||||||
|
import {WebAccessTypes} from "../../types/WebAccessTypes";
|
||||||
|
import {SettingsUser} from "../../types/SettingsUser";
|
||||||
const httpLogger = log4js.getLogger('http');
|
const httpLogger = log4js.getLogger('http');
|
||||||
const settings = require('../../utils/Settings');
|
const settings = require('../../utils/Settings');
|
||||||
const hooks = require('../../../static/js/pluginfw/hooks');
|
const hooks = require('../../../static/js/pluginfw/hooks');
|
||||||
|
@ -10,14 +13,15 @@ const readOnlyManager = require('../../db/ReadOnlyManager');
|
||||||
hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
|
hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
|
||||||
|
|
||||||
// Promisified wrapper around hooks.aCallFirst.
|
// Promisified wrapper around hooks.aCallFirst.
|
||||||
const aCallFirst = (hookName, context, pred = null) => new Promise((resolve, reject) => {
|
const aCallFirst = (hookName: string, context:any, pred = null) => new Promise((resolve, reject) => {
|
||||||
hooks.aCallFirst(hookName, context, (err, r) => err != null ? reject(err) : resolve(r), pred);
|
hooks.aCallFirst(hookName, context, (err:any, r: unknown) => err != null ? reject(err) : resolve(r), pred);
|
||||||
});
|
});
|
||||||
|
|
||||||
const aCallFirst0 =
|
const aCallFirst0 =
|
||||||
async (hookName, context, pred = null) => (await aCallFirst(hookName, context, pred))[0];
|
// @ts-ignore
|
||||||
|
async (hookName: string, context:any, pred = null) => (await aCallFirst(hookName, context, pred))[0];
|
||||||
|
|
||||||
exports.normalizeAuthzLevel = (level) => {
|
exports.normalizeAuthzLevel = (level: string|boolean) => {
|
||||||
if (!level) return false;
|
if (!level) return false;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case true:
|
case true:
|
||||||
|
@ -32,7 +36,7 @@ exports.normalizeAuthzLevel = (level) => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.userCanModify = (padId, req) => {
|
exports.userCanModify = (padId: string, req: SocketClientRequest) => {
|
||||||
if (readOnlyManager.isReadOnlyId(padId)) return false;
|
if (readOnlyManager.isReadOnlyId(padId)) return false;
|
||||||
if (!settings.requireAuthentication) return true;
|
if (!settings.requireAuthentication) return true;
|
||||||
const {session: {user} = {}} = req;
|
const {session: {user} = {}} = req;
|
||||||
|
@ -45,7 +49,7 @@ exports.userCanModify = (padId, req) => {
|
||||||
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
|
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
|
||||||
exports.authnFailureDelayMs = 1000;
|
exports.authnFailureDelayMs = 1000;
|
||||||
|
|
||||||
const checkAccess = async (req, res, next) => {
|
const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
|
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -54,9 +58,9 @@ const checkAccess = async (req, res, next) => {
|
||||||
// use the preAuthzFailure hook to override the default 403 error.
|
// use the preAuthzFailure hook to override the default 403 error.
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
let results;
|
let results: null|boolean[];
|
||||||
let skip = false;
|
let skip = false;
|
||||||
const preAuthorizeNext = (...args) => { skip = true; next(...args); };
|
const preAuthorizeNext = (...args:any) => { skip = true; next(...args); };
|
||||||
try {
|
try {
|
||||||
results = await aCallFirst('preAuthorize', {req, res, next: preAuthorizeNext},
|
results = await aCallFirst('preAuthorize', {req, res, next: preAuthorizeNext},
|
||||||
// This predicate will cause aCallFirst to call the hook functions one at a time until one
|
// This predicate will cause aCallFirst to call the hook functions one at a time until one
|
||||||
|
@ -64,8 +68,9 @@ const checkAccess = async (req, res, next) => {
|
||||||
// page, truthy entries are filtered out before checking to see whether the list is empty.
|
// page, truthy entries are filtered out before checking to see whether the list is empty.
|
||||||
// This prevents plugin authors from accidentally granting admin privileges to the general
|
// This prevents plugin authors from accidentally granting admin privileges to the general
|
||||||
// public.
|
// public.
|
||||||
(r) => (skip || (r != null && r.filter((x) => (!requireAdmin || !x)).length > 0)));
|
// @ts-ignore
|
||||||
} catch (err) {
|
(r) => (skip || (r != null && r.filter((x) => (!requireAdmin || !x)).length > 0))) as boolean[];
|
||||||
|
} catch (err:any) {
|
||||||
httpLogger.error(`Error in preAuthorize hook: ${err.stack || err.toString()}`);
|
httpLogger.error(`Error in preAuthorize hook: ${err.stack || err.toString()}`);
|
||||||
if (!skip) res.status(500).send('Internal Server Error');
|
if (!skip) res.status(500).send('Internal Server Error');
|
||||||
return;
|
return;
|
||||||
|
@ -87,7 +92,7 @@ const checkAccess = async (req, res, next) => {
|
||||||
// This helper is used in steps 2 and 4 below, so it may be called twice per access: once before
|
// This helper is used in steps 2 and 4 below, so it may be called twice per access: once before
|
||||||
// authentication is checked and once after (if settings.requireAuthorization is true).
|
// authentication is checked and once after (if settings.requireAuthorization is true).
|
||||||
const authorize = async () => {
|
const authorize = async () => {
|
||||||
const grant = async (level) => {
|
const grant = async (level: string|false) => {
|
||||||
level = exports.normalizeAuthzLevel(level);
|
level = exports.normalizeAuthzLevel(level);
|
||||||
if (!level) return false;
|
if (!level) return false;
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
@ -132,7 +137,7 @@ const checkAccess = async (req, res, next) => {
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (settings.users == null) settings.users = {};
|
if (settings.users == null) settings.users = {};
|
||||||
const ctx = {req, res, users: settings.users, next};
|
const ctx:WebAccessTypes = {req, res, users: settings.users, next};
|
||||||
// If the HTTP basic auth header is present, extract the username and password so it can be given
|
// If the HTTP basic auth header is present, extract the username and password so it can be given
|
||||||
// to authn plugins.
|
// to authn plugins.
|
||||||
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
|
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
|
||||||
|
@ -148,7 +153,8 @@ const checkAccess = async (req, res, next) => {
|
||||||
}
|
}
|
||||||
if (!(await aCallFirst0('authenticate', ctx))) {
|
if (!(await aCallFirst0('authenticate', ctx))) {
|
||||||
// Fall back to HTTP basic auth.
|
// Fall back to HTTP basic auth.
|
||||||
const {[ctx.username]: {password} = {}} = settings.users;
|
// @ts-ignore
|
||||||
|
const {[ctx.username]: {password} = {}} = settings.users as SettingsUser;
|
||||||
|
|
||||||
if (!httpBasicAuth ||
|
if (!httpBasicAuth ||
|
||||||
!ctx.username ||
|
!ctx.username ||
|
||||||
|
@ -193,6 +199,6 @@ const checkAccess = async (req, res, next) => {
|
||||||
* Express middleware to authenticate the user and check authorization. Must be installed after the
|
* Express middleware to authenticate the user and check authorization. Must be installed after the
|
||||||
* express-session middleware.
|
* express-session middleware.
|
||||||
*/
|
*/
|
||||||
exports.checkAccess = (req, res, next) => {
|
exports.checkAccess = (req:any, res:any, next:Function) => {
|
||||||
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
|
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
|
||||||
};
|
};
|
0
src/node/types/APIHandlerType.ts
Normal file
0
src/node/types/APIHandlerType.ts
Normal file
3
src/node/types/ChangeSet.ts
Normal file
3
src/node/types/ChangeSet.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export type ChangeSet = {
|
||||||
|
|
||||||
|
}
|
|
@ -13,8 +13,12 @@ export type PadType = {
|
||||||
getAllAuthorColors: ()=>Promise<MapArrayType<string>>,
|
getAllAuthorColors: ()=>Promise<MapArrayType<string>>,
|
||||||
remove: ()=>Promise<void>,
|
remove: ()=>Promise<void>,
|
||||||
text: ()=>string,
|
text: ()=>string,
|
||||||
setText: (text: string)=>Promise<void>,
|
setText: (text: string, authorId?: string)=>Promise<void>,
|
||||||
appendText: (text: string)=>Promise<void>,
|
appendText: (text: string)=>Promise<void>,
|
||||||
|
getHeadRevisionNumber: ()=>number,
|
||||||
|
getRevisionDate: (rev: number)=>Promise<number>,
|
||||||
|
getRevisionChangeset: (rev: number)=>Promise<AChangeSet>,
|
||||||
|
appendRevision: (changeset: AChangeSet, author: string)=>Promise<void>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
3
src/node/types/SocketAcknowledge.ts
Normal file
3
src/node/types/SocketAcknowledge.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export type SocketAcknowledge = {
|
||||||
|
|
||||||
|
}
|
30
src/node/types/SocketClientRequest.ts
Normal file
30
src/node/types/SocketClientRequest.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export type SocketClientRequest = {
|
||||||
|
session: {
|
||||||
|
user: {
|
||||||
|
username: string;
|
||||||
|
readOnly: boolean;
|
||||||
|
padAuthorizations: {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type PadUserInfo = {
|
||||||
|
data: {
|
||||||
|
userInfo: {
|
||||||
|
name: string|null;
|
||||||
|
colorId: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type ChangesetRequest = {
|
||||||
|
data: {
|
||||||
|
granularity: number;
|
||||||
|
start: number;
|
||||||
|
requestID: string;
|
||||||
|
}
|
||||||
|
}
|
3
src/node/types/SocketModule.ts
Normal file
3
src/node/types/SocketModule.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export type SocketModule = {
|
||||||
|
setSocketIO: (io: any) => void;
|
||||||
|
}
|
10
src/node/types/WebAccessTypes.ts
Normal file
10
src/node/types/WebAccessTypes.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {SettingsUser} from "./SettingsUser";
|
||||||
|
|
||||||
|
export type WebAccessTypes = {
|
||||||
|
username?: string|null;
|
||||||
|
password?: string;
|
||||||
|
req:any;
|
||||||
|
res:any;
|
||||||
|
next:any;
|
||||||
|
users: SettingsUser;
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import {APool} from "../types/PadType";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
* 2014 John McLear (Etherpad Foundation / McLear Ltd)
|
||||||
*
|
*
|
||||||
|
@ -22,17 +24,17 @@ const Stream = require('./Stream');
|
||||||
const authorManager = require('../db/AuthorManager');
|
const authorManager = require('../db/AuthorManager');
|
||||||
const db = require('../db/DB');
|
const db = require('../db/DB');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const supportedElems = require('../../static/js/contentcollector').supportedElems;
|
const supportedElems = require('../../static/js/contentcollector').supportedElems;
|
||||||
const ueberdb = require('ueberdb2');
|
import ueberdb from 'ueberdb2';
|
||||||
|
|
||||||
const logger = log4js.getLogger('ImportEtherpad');
|
const logger = log4js.getLogger('ImportEtherpad');
|
||||||
|
|
||||||
exports.setPadRaw = async (padId, r, authorId = '') => {
|
exports.setPadRaw = async (padId: string, r: string, authorId = '') => {
|
||||||
const records = JSON.parse(r);
|
const records = JSON.parse(r);
|
||||||
|
|
||||||
// get supported block Elements from plugins, we will use this later.
|
// get supported block Elements from plugins, we will use this later.
|
||||||
hooks.callAll('ccRegisterBlockElements').forEach((element) => {
|
hooks.callAll('ccRegisterBlockElements').forEach((element:any) => {
|
||||||
supportedElems.add(element);
|
supportedElems.add(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,8 +45,8 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
|
||||||
'pad',
|
'pad',
|
||||||
];
|
];
|
||||||
|
|
||||||
let originalPadId = null;
|
let originalPadId:string|null = null;
|
||||||
const checkOriginalPadId = (padId) => {
|
const checkOriginalPadId = (padId: string) => {
|
||||||
if (originalPadId == null) originalPadId = padId;
|
if (originalPadId == null) originalPadId = padId;
|
||||||
if (originalPadId !== padId) throw new Error('unexpected pad ID in record');
|
if (originalPadId !== padId) throw new Error('unexpected pad ID in record');
|
||||||
};
|
};
|
||||||
|
@ -57,7 +59,10 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
|
||||||
const padDb = new ueberdb.Database('memory', {data});
|
const padDb = new ueberdb.Database('memory', {data});
|
||||||
await padDb.init();
|
await padDb.init();
|
||||||
try {
|
try {
|
||||||
const processRecord = async (key, value) => {
|
const processRecord = async (key:string, value: null|{
|
||||||
|
padIDs: string|Record<string, unknown>,
|
||||||
|
pool: APool
|
||||||
|
}) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
const keyParts = key.split(':');
|
const keyParts = key.split(':');
|
||||||
const [prefix, id] = keyParts;
|
const [prefix, id] = keyParts;
|
||||||
|
@ -79,7 +84,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
|
||||||
if (prefix === 'pad' && keyParts.length === 2) {
|
if (prefix === 'pad' && keyParts.length === 2) {
|
||||||
const pool = new AttributePool().fromJsonable(value.pool);
|
const pool = new AttributePool().fromJsonable(value.pool);
|
||||||
const unsupportedElements = new Set();
|
const unsupportedElements = new Set();
|
||||||
pool.eachAttrib((k, v) => {
|
pool.eachAttrib((k: string, v:any) => {
|
||||||
if (!supportedElems.has(k)) unsupportedElements.add(k);
|
if (!supportedElems.has(k)) unsupportedElements.add(k);
|
||||||
});
|
});
|
||||||
if (unsupportedElements.size) {
|
if (unsupportedElements.size) {
|
||||||
|
@ -94,8 +99,10 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
|
||||||
`importEtherpad hook function processes it: ${key}`);
|
`importEtherpad hook function processes it: ${key}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
await padDb.set(key, value);
|
await padDb.set(key, value);
|
||||||
};
|
};
|
||||||
|
// @ts-ignore
|
||||||
const readOps = new Stream(Object.entries(records)).map(([k, v]) => processRecord(k, v));
|
const readOps = new Stream(Object.entries(records)).map(([k, v]) => processRecord(k, v));
|
||||||
for (const op of readOps.batch(100).buffer(99)) await op;
|
for (const op of readOps.batch(100).buffer(99)) await op;
|
||||||
|
|
|
@ -15,15 +15,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const log4js = require('log4js');
|
import log4js from 'log4js';
|
||||||
const Changeset = require('../../static/js/Changeset');
|
const Changeset = require('../../static/js/Changeset');
|
||||||
const contentcollector = require('../../static/js/contentcollector');
|
const contentcollector = require('../../static/js/contentcollector');
|
||||||
const jsdom = require('jsdom');
|
import jsdom from 'jsdom';
|
||||||
|
import {PadType} from "../types/PadType";
|
||||||
|
|
||||||
const apiLogger = log4js.getLogger('ImportHtml');
|
const apiLogger = log4js.getLogger('ImportHtml');
|
||||||
let processor;
|
let processor:any;
|
||||||
|
|
||||||
exports.setPadHTML = async (pad, html, authorId = '') => {
|
exports.setPadHTML = async (pad: PadType, html:string, authorId = '') => {
|
||||||
if (processor == null) {
|
if (processor == null) {
|
||||||
const [{rehype}, {default: minifyWhitespace}] =
|
const [{rehype}, {default: minifyWhitespace}] =
|
||||||
await Promise.all([import('rehype'), import('rehype-minify-whitespace')]);
|
await Promise.all([import('rehype'), import('rehype-minify-whitespace')]);
|
||||||
|
@ -46,7 +47,7 @@ exports.setPadHTML = async (pad, html, authorId = '') => {
|
||||||
try {
|
try {
|
||||||
// we use a try here because if the HTML is bad it will blow up
|
// we use a try here because if the HTML is bad it will blow up
|
||||||
cc.collectContent(document.body);
|
cc.collectContent(document.body);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
apiLogger.warn(`Error processing HTML: ${err.stack || err}`);
|
apiLogger.warn(`Error processing HTML: ${err.stack || err}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
|
@ -81,6 +81,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/async": "^3.2.24",
|
"@types/async": "^3.2.24",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/http-errors": "^2.0.4",
|
||||||
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/mocha": "^10.0.6",
|
"@types/mocha": "^10.0.6",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
"@types/sinon": "^17.0.3",
|
"@types/sinon": "^17.0.3",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"lib": ["es6"],
|
"lib": ["ES2023"],
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
/* Modules */
|
/* Modules */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue