chore: added esm

This commit is contained in:
SamTV12345 2025-08-05 21:27:30 +02:00
parent aa766825cb
commit 7339c5a1b2
37 changed files with 2793 additions and 1155 deletions

1504
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -8,3 +8,4 @@ onlyBuiltDependencies:
- '@scarf/scarf'
- '@swc/core'
- esbuild
- protobufjs

File diff suppressed because it is too large Load diff

View file

@ -19,12 +19,12 @@
* limitations under the License.
*/
const db = require('./DB');
const CustomError = require('../utils/customError');
import db from './DB';
import CustomError from '../utils/customError';
const hooks = require('../../static/js/pluginfw/hooks');
import padutils, {randomString} from "../../static/js/pad_utils";
import {randomString} from "../../static/js/pad_utils";
exports.getColorPalette = () => [
const getColorPalette = () => [
'#ffc7c7',
'#fff1c7',
'#e3ffc7',
@ -95,7 +95,7 @@ exports.getColorPalette = () => [
* Checks if the author exists
* @param {String} authorID The id of the author
*/
exports.doesAuthorExist = async (authorID: string) => {
const doesAuthorExist = async (authorID: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
return author != null;
@ -105,7 +105,7 @@ exports.doesAuthorExist = async (authorID: string) => {
exported for backwards compatibility
@param {String} authorID The id of the author
*/
exports.doesAuthorExists = exports.doesAuthorExist;
const doesAuthorExists = doesAuthorExist;
/**
@ -120,7 +120,7 @@ const mapAuthorWithDBKey = async (mapperkey: string, mapper:string) => {
if (author == null) {
// there is no author with this mapper, so create one
const author = await exports.createAuthor(null);
const author = await createAuthor(null);
// create the token2author relation
await db.set(`${mapperkey}:${mapper}`, author.authorID);
@ -155,36 +155,24 @@ const getAuthor4Token = async (token: string) => {
* @param {Object} user
* @return {Promise<*>}
*/
exports.getAuthorId = async (token: string, user: object) => {
const getAuthorId = async (token: string, user: object | undefined) => {
const context = {dbKey: token, token, user};
let [authorId] = await hooks.aCallFirst('getAuthorId', context);
if (!authorId) authorId = await getAuthor4Token(context.dbKey);
return authorId;
};
/**
* Returns the AuthorID for a token.
*
* @deprecated Use `getAuthorId` instead.
* @param {String} token The token
*/
exports.getAuthor4Token = async (token: string) => {
padutils.warnDeprecated(
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
return await getAuthor4Token(token);
};
/**
* Returns the AuthorID for a mapper.
* @param {String} authorMapper The mapper
* @param {String} name The name of the author (optional)
*/
exports.createAuthorIfNotExistsFor = async (authorMapper: string, name: string) => {
const createAuthorIfNotExistsFor = async (authorMapper: string, name: string) => {
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
if (name) {
// set the name of this author
await exports.setAuthorName(author.authorID, name);
await setAuthorName(author.authorID, name);
}
return author;
@ -195,13 +183,13 @@ exports.createAuthorIfNotExistsFor = async (authorMapper: string, name: string)
* Internal function that creates the database entry for an author
* @param {String} name The name of the author
*/
exports.createAuthor = async (name: string) => {
const createAuthor = async (name: string| null) => {
// create the new author name
const author = `a.${randomString(16)}`;
// create the globalAuthors db entry
const authorObj = {
colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
colorId: Math.floor(Math.random() * (getColorPalette().length)),
name,
timestamp: Date.now(),
};
@ -216,41 +204,41 @@ exports.createAuthor = async (name: string) => {
* Returns the Author Obj of the author
* @param {String} author The id of the author
*/
exports.getAuthor = async (author: string) => await db.get(`globalAuthor:${author}`);
const getAuthor = async (author: string) => await db.get(`globalAuthor:${author}`);
/**
* Returns the color Id of the author
* @param {String} author The id of the author
*/
exports.getAuthorColorId = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
const getAuthorColorId = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
/**
* Sets the color Id of the author
* @param {String} author The id of the author
* @param {String} colorId The color id of the author
*/
exports.setAuthorColorId = async (author: string, colorId: string) => await db.setSub(
const setAuthorColorId = async (author: string, colorId: string) => await db.setSub(
`globalAuthor:${author}`, ['colorId'], colorId);
/**
* Returns the name of the author
* @param {String} author The id of the author
*/
exports.getAuthorName = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['name']);
const getAuthorName = async (author: string) => await db.getSub(`globalAuthor:${author}`, ['name']);
/**
* Sets the name of the author
* @param {String} author The id of the author
* @param {String} name The name of the author
*/
exports.setAuthorName = async (author: string, name: string) => await db.setSub(
const setAuthorName = async (author: string, name: string|null) => await db.setSub(
`globalAuthor:${author}`, ['name'], name);
/**
* Returns an array of all pads this author contributed to
* @param {String} authorID The id of the author
*/
exports.listPadsOfAuthor = async (authorID: string) => {
const listPadsOfAuthor = async (authorID: string) => {
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
@ -275,7 +263,7 @@ exports.listPadsOfAuthor = async (authorID: string) => {
* @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.addPad = async (authorID: string, padID: string) => {
const addPad = async (authorID: string, padID: string) => {
// get the entry
const author = await db.get(`globalAuthor:${authorID}`);
@ -302,7 +290,7 @@ exports.addPad = async (authorID: string, padID: string) => {
* @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.removePad = async (authorID: string, padID: string) => {
const removePad = async (authorID: string, padID: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
if (author == null) return;
@ -313,3 +301,21 @@ exports.removePad = async (authorID: string, padID: string) => {
await db.set(`globalAuthor:${authorID}`, author);
}
};
export default {
doesAuthorExist,
doesAuthorExists,
getAuthor4Token,
getAuthorId,
createAuthorIfNotExistsFor,
createAuthor,
getAuthor,
getAuthorColorId,
setAuthorColorId,
getAuthorName,
setAuthorName,
listPadsOfAuthor,
addPad,
removePad,
getColorPalette,
}

View file

@ -24,37 +24,56 @@
import {Database, DatabaseType} from 'ueberdb2';
import settings from '../utils/Settings';
import log4js from 'log4js';
const stats = require('../stats')
import stats from '../stats'
const logger = log4js.getLogger('ueberDB');
/**
* The UeberDB Object that provides the database functions
*/
exports.db = null;
export let db: Database | null = null;
export type DBFunctionsPromisified = {
get: (key: string, cb?: Function) => Promise<any>,
set: (key:string, value:object|string, callback?:Function) => Promise<void>,
findKeys: (key:string, notKey:string|null, callback?:Function) => Promise<string[]>,
getSub: (key: string, sub: string[]) => Promise<any>,
setSub: (key: string, sub: string[], value: any) => Promise<void>,
remove: (key:string, cb?: Function| null) => Promise<void>,
}
export const asyncFunctions: DBFunctionsPromisified = {
} as DBFunctionsPromisified
export default asyncFunctions
/**
* Initializes the database with the settings provided by the settings module
*/
exports.init = async () => {
exports.db = new Database(settings.dbType as DatabaseType, settings.dbSettings, null, logger);
await exports.db.init();
if (exports.db.metrics != null) {
for (const [metric, value] of Object.entries(exports.db.metrics)) {
export const init = async () => {
db = new Database(settings.dbType as DatabaseType, settings.dbSettings, null, logger);
await db.init();
if (db.metrics != null) {
for (const [metric, value] of Object.entries(db.metrics)) {
if (typeof value !== 'number') continue;
stats.gauge(`ueberdb_${metric}`, () => exports.db.metrics[metric]);
stats.gauge(`ueberdb_${metric}`, () => db!.metrics[metric]);
}
}
for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
const f = exports.db[fn];
exports[fn] = async (...args:string[]) => await f.call(exports.db, ...args);
Object.setPrototypeOf(exports[fn], Object.getPrototypeOf(f));
Object.defineProperties(exports[fn], Object.getOwnPropertyDescriptors(f));
// @ts-ignore
const f = db[fn];
// @ts-ignore
asyncFunctions[fn] = async (...args:string[]) => await f.call(db, ...args);
// @ts-ignore
Object.setPrototypeOf(asyncFunctions[fn], Object.getPrototypeOf(f));
// @ts-ignore
Object.defineProperties(asyncFunctions[fn], Object.getOwnPropertyDescriptors(f));
}
};
exports.shutdown = async (hookName: string, context:any) => {
if (exports.db != null) await exports.db.close();
exports.db = null;
export const shutdown = async (hookName: string, context:any) => {
if (db != null) await db.close();
db = null;
logger.log('Database closed');
};

View file

@ -19,17 +19,17 @@
* limitations under the License.
*/
const CustomError = require('../utils/customError');
import CustomError from '../utils/customError';
import {randomString} from "../../static/js/pad_utils";
const db = require('./DB');
const padManager = require('./PadManager');
const sessionManager = require('./SessionManager');
import db from './DB';
import padManager from './PadManager';
import sessionManager from './SessionManager';
/**
* Lists all groups
* @return {Promise<{groupIDs: string[]}>} The ids of all groups
*/
exports.listAllGroups = async () => {
export const listAllGroups = async () => {
let groups = await db.get('groups');
groups = groups || {};
@ -42,7 +42,7 @@ exports.listAllGroups = async () => {
* @param {String} groupID The id of the group
* @return {Promise<void>} Resolves when the group is deleted
*/
exports.deleteGroup = async (groupID: string): Promise<void> => {
export const deleteGroup = async (groupID: string): Promise<void> => {
const group = await db.get(`group:${groupID}`);
// ensure group exists
@ -82,7 +82,7 @@ exports.deleteGroup = async (groupID: string): Promise<void> => {
* @param {String} groupID the id of the group to delete
* @return {Promise<boolean>} Resolves to true if the group exists
*/
exports.doesGroupExist = async (groupID: string) => {
const doesGroupExist = async (groupID: string) => {
// try to get the group entry
const group = await db.get(`group:${groupID}`);
@ -93,7 +93,7 @@ exports.doesGroupExist = async (groupID: string) => {
* Creates a new group
* @return {Promise<{groupID: string}>} the id of the new group
*/
exports.createGroup = async () => {
const createGroup = async () => {
const groupID = `g.${randomString(16)}`;
await db.set(`group:${groupID}`, {pads: {}, mappings: {}});
// Add the group to the `groups` record after the group's individual record is created so that
@ -108,13 +108,13 @@ exports.createGroup = async () => {
* @param groupMapper the mapper of the group
* @return {Promise<{groupID: string}|{groupID: *}>} a promise that resolves to the group ID
*/
exports.createGroupIfNotExistsFor = async (groupMapper: string|object) => {
const createGroupIfNotExistsFor = async (groupMapper: string|object) => {
if (typeof groupMapper !== 'string') {
throw new CustomError('groupMapper is not a string', 'apierror');
}
const groupID = await db.get(`mapper2group:${groupMapper}`);
if (groupID && await exports.doesGroupExist(groupID)) return {groupID};
const result = await exports.createGroup();
if (groupID && await doesGroupExist(groupID)) return {groupID};
const result = await createGroup();
await Promise.all([
db.set(`mapper2group:${groupMapper}`, result.groupID),
// Remember the mapping in the group record so that it can be cleaned up when the group is
@ -134,12 +134,12 @@ exports.createGroupIfNotExistsFor = async (groupMapper: string|object) => {
* @param {String} authorId The id of the author
* @return {Promise<{padID: string}>} a promise that resolves to the id of the new pad
*/
exports.createGroupPad = async (groupID: string, padName: string, text: string, authorId: string = ''): Promise<{ padID: string; }> => {
const createGroupPad = async (groupID: string, padName: string, text: string, authorId: string = ''): Promise<{ padID: string; }> => {
// create the padID
const padID = `${groupID}$${padName}`;
// ensure group exists
const groupExists = await exports.doesGroupExist(groupID);
const groupExists = await doesGroupExist(groupID);
if (!groupExists) {
throw new CustomError('groupID does not exist', 'apierror');
@ -167,8 +167,8 @@ exports.createGroupPad = async (groupID: string, padName: string, text: string,
* @param {String} groupID The id of the group
* @return {Promise<{padIDs: string[]}>} a promise that resolves to the ids of all pads of the group
*/
exports.listPads = async (groupID: string): Promise<{ padIDs: string[]; }> => {
const exists = await exports.doesGroupExist(groupID);
const listPads = async (groupID: string): Promise<{ padIDs: string[]; }> => {
const exists = await doesGroupExist(groupID);
// ensure the group exists
if (!exists) {
@ -181,3 +181,13 @@ exports.listPads = async (groupID: string): Promise<{ padIDs: string[]; }> => {
return {padIDs};
};
export default {
listAllGroups,
deleteGroup,
doesGroupExist,
createGroup,
createGroupIfNotExistsFor,
createGroupPad,
listPads,
}

View file

@ -11,15 +11,15 @@ import AttributeMap from '../../static/js/AttributeMap';
import {applyToAText, checkRep, copyAText, deserializeOps, makeAText, makeSplice, opsFromAText, pack, unpack} from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage';
import AttributePool from '../../static/js/AttributePool';
const Stream = require('../utils/Stream');
const assert = require('assert').strict;
const db = require('./DB');
import Stream from '../utils/Stream';
import {strict as assert} from 'node:assert'
import db, {DBFunctionsPromisified} from './DB';
import settings from '../utils/Settings';
const authorManager = require('./AuthorManager');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const groupManager = require('./GroupManager');
const CustomError = require('../utils/customError');
import authorManager from "./AuthorManager";
import padManager from './PadManager'
import padMessageHandler from '../handler/PadMessageHandler';
import groupManager from './GroupManager';
import CustomError from '../utils/customError';
import readOnlyManager from './ReadOnlyManager';
import randomString from '../utils/randomstring';
const hooks = require('../../static/js/pluginfw/hooks');
@ -33,19 +33,19 @@ import {timesLimit} from "async";
* @param {String} txt The text to clean
* @returns {String} The cleaned text
*/
exports.cleanText = (txt:string): string => txt.replace(/\r\n/g, '\n')
export const cleanText = (txt:string): string => txt.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\xa0/g, ' ');
class Pad {
private db: Database;
private atext: AText;
private pool: AttributePool;
private head: number;
private chatHead: number;
private db: DBFunctionsPromisified;
atext: AText;
pool: AttributePool;
head: number;
chatHead: number;
private publicStatus: boolean;
private id: string;
id: string;
private savedRevisions: any[];
/**
* @param id
@ -230,9 +230,9 @@ class Pad {
const colorPalette = authorManager.getColorPalette();
await Promise.all(
authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId:string) => {
authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId:number) => {
// colorId might be a hex color or an number out of the palette
returnTable[authorId] = colorPalette[colorId] || colorId;
returnTable[authorId] = colorPalette[colorId] || colorId.toString();
})));
return returnTable;
@ -287,7 +287,7 @@ class Pad {
const orig = this.text();
assert(orig.endsWith('\n'));
if (start + ndel > orig.length) throw new RangeError('start/delete past the end of the text');
ins = exports.cleanText(ins);
ins = cleanText(ins);
const willEndWithNewline =
start + ndel < orig.length || // Keeping last char (which is guaranteed to be a newline).
ins.endsWith('\n') ||
@ -352,6 +352,9 @@ class Pad {
const entry = await this.db.get(`pad:${this.id}:chat:${entryNum}`);
if (entry == null) return null;
const message = ChatMessage.fromObject(entry);
if (message.authorId == null) {
return null
}
message.displayName = await authorManager.getAuthorName(message.authorId);
return message;
}
@ -363,7 +366,7 @@ class Pad {
* (inclusive), in order. Note: `start` and `end` form a closed interval, not a half-open
* interval as is typical in code.
*/
async getChatMessages(start: string, end: number) {
async getChatMessages(start: number, end: number) {
const entries =
await Promise.all(Stream.range(start, end + 1).map(this.getChatMessage.bind(this)));
@ -392,7 +395,7 @@ class Pad {
const context = {pad: this, authorId, type: 'text', content: settings.defaultPadText};
await hooks.aCallAll('padDefaultContent', context);
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
text = exports.cleanText(context.content);
text = cleanText(context.content);
}
const firstChangeset = makeSplice('\n', 0, 0, text);
await this.appendRevision(firstChangeset, authorId);
@ -620,7 +623,7 @@ class Pad {
await this.saveToDatabase();
}
async addSavedRevision(revNum: string, savedById: string, label: string) {
async addSavedRevision(revNum: number, savedById: string, label?: string) {
// if this revision is already saved, return silently
for (const i in this.savedRevisions) {
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
@ -765,4 +768,4 @@ class Pad {
await hooks.aCallAll('padCheck', {pad: this});
}
}
exports.Pad = Pad;
export default Pad

View file

@ -22,9 +22,9 @@
import {MapArrayType} from "../types/MapType";
import {PadType} from "../types/PadType";
const CustomError = require('../utils/customError');
const Pad = require('../db/Pad');
const db = require('./DB');
import CustomError from '../utils/customError';
import Pad from '../db/Pad';
import db from './DB';
import settings from '../utils/Settings';
/**
@ -97,7 +97,7 @@ const padList = new class {
}
}();
// initialises the all-knowing data structure
// initializes the all-knowing data structure
/**
* Returns a Pad Object with the callback
@ -106,9 +106,9 @@ const padList = new class {
* @param {string} [authorId] - Optional author ID of the user that initiated the pad creation (if
* applicable).
*/
exports.getPad = async (id: string, text?: string|null, authorId:string|null = ''):Promise<PadType> => {
export const getPad = async (id: string, text?: string|null, authorId:string|null = ''):Promise<Pad> => {
// check if this is a valid padId
if (!exports.isValidPadId(id)) {
if (!isValidPadId(id)) {
throw new CustomError(`${id} is not a valid padId`, 'apierror');
}
@ -133,7 +133,7 @@ exports.getPad = async (id: string, text?: string|null, authorId:string|null = '
}
// try to load pad
pad = new Pad.Pad(id);
pad = new Pad(id);
// initialize the pad
await pad.init(text, authorId);
@ -143,7 +143,7 @@ exports.getPad = async (id: string, text?: string|null, authorId:string|null = '
return pad;
};
exports.listAllPads = async () => {
export const listAllPads = async () => {
const padIDs = await padList.getPads();
return {padIDs};
@ -153,14 +153,14 @@ exports.listAllPads = async () => {
// checks if a pad exists
exports.doesPadExist = async (padId: string) => {
export const doesPadExist = async (padId: string) => {
const value = await db.get(`pad:${padId}`);
return (value != null && value.atext);
};
// alias for backwards compatibility
exports.doesPadExists = exports.doesPadExist;
export const doesPadExists = doesPadExist;
/**
* An array of padId transformations. These represent changes in pad name policy over
@ -172,9 +172,9 @@ const padIdTransforms = [
];
// returns a sanitized padId, respecting legacy pad id formats
exports.sanitizePadId = async (padId: string) => {
export const sanitizePadId = async (padId: string) => {
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
const exists = await exports.doesPadExist(padId);
const exists = await doesPadExist(padId);
if (exists) {
return padId;
@ -192,19 +192,33 @@ exports.sanitizePadId = async (padId: string) => {
return padId;
};
exports.isValidPadId = (padId: string) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
export const isValidPadId = (padId: string) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
/**
* Removes the pad from database and unloads it.
*/
exports.removePad = async (padId: string) => {
export const removePad = async (padId: string) => {
const p = db.remove(`pad:${padId}`);
exports.unloadPad(padId);
unloadPad(padId);
padList.removePad(padId);
await p;
};
// removes a pad from the cache
exports.unloadPad = (padId: string) => {
export const unloadPad = (padId: string) => {
globalPads.remove(padId);
};
export default {
getPad,
listAllPads,
doesPadExist,
doesPadExists: doesPadExist, // alias for backwards compatibility
sanitizePadId,
isValidPadId,
removePad,
unloadPad,
globalPads,
padList,
padIdTransforms,
}

View file

@ -1,4 +1,3 @@
'use strict';
/**
* The ReadOnlyManager manages the database and rendering releated to read only pads
*/
@ -20,7 +19,7 @@
*/
const db = require('./DB');
import db from './DB'
import randomString from '../utils/randomstring';

View file

@ -21,14 +21,14 @@
import {UserSettingsObject} from "../types/UserSettingsObject";
const authorManager = require('./AuthorManager');
import authorManager from "./AuthorManager";
const hooks = require('../../static/js/pluginfw/hooks');
const padManager = require('./PadManager');
import padManager from './PadManager'
import readOnlyManager from './ReadOnlyManager';
const sessionManager = require('./SessionManager');
import sessionManager from './SessionManager';
import settings from '../utils/Settings';
const webaccess = require('../hooks/express/webaccess');
const log4js = require('log4js');
import webaccess from '../hooks/express/webaccess';
import log4js from 'log4js';
const authLogger = log4js.getLogger('auth');
import padutils from '../../static/js/pad_utils'
@ -57,7 +57,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* @param {Object} userSettings
* @return {DENY|{accessStatus: String, authorID: String}}
*/
exports.checkAccess = async (padID:string, sessionCookie:string, token:string, userSettings:UserSettingsObject) => {
export const checkAccess = async (padID:string, sessionCookie:string, token:string, userSettings:UserSettingsObject | undefined) => {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;
@ -148,3 +148,7 @@ exports.checkAccess = async (padID:string, sessionCookie:string, token:string, u
return grant;
};
export default {
checkAccess
}

View file

@ -20,12 +20,12 @@
* limitations under the License.
*/
const CustomError = require('../utils/customError');
import CustomError from '../utils/customError';
import {firstSatisfies} from '../utils/promises';
import randomString from '../utils/randomstring';
const db = require('./DB');
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');
import db from './DB';
import groupManager from './GroupManager';
import authorManager from './AuthorManager';
/**
* Finds the author ID for a session with matching ID and group.
@ -36,7 +36,7 @@ const authorManager = require('./AuthorManager');
* sessionCookie, and is bound to a group with the given ID, then this returns the author ID
* bound to the session. Otherwise, returns undefined.
*/
exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
const findAuthorID = async (groupID:string, sessionCookie: string) => {
if (!sessionCookie) return undefined;
/*
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
@ -64,7 +64,7 @@ exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
const sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
const sessionInfoPromises = sessionIDs.map(async (id) => {
try {
return await exports.getSessionInfo(id);
return await getSessionInfo(id);
} catch (err:any) {
if (err.message === 'sessionID does not exist') {
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
@ -89,7 +89,7 @@ exports.findAuthorID = async (groupID:string, sessionCookie: string) => {
* @param {String} sessionID The id of the session
* @return {Promise<boolean>} Resolves to true if the session exists
*/
exports.doesSessionExist = async (sessionID: string) => {
const doesSessionExist = async (sessionID: string) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
return (session != null);
@ -102,7 +102,7 @@ exports.doesSessionExist = async (sessionID: string) => {
* @param {Number} validUntil The unix timestamp when the session should expire
* @return {Promise<{sessionID: string}>} the id of the new session
*/
exports.createSession = async (groupID: string, authorID: string, validUntil: number) => {
const createSession = async (groupID: string, authorID: string, validUntil: number|string) => {
// check if the group exists
const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) {
@ -163,7 +163,7 @@ exports.createSession = async (groupID: string, authorID: string, validUntil: nu
* @param {String} sessionID The id of the session
* @return {Promise<Object>} the sessioninfos
*/
exports.getSessionInfo = async (sessionID:string) => {
const getSessionInfo = async (sessionID:string) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
@ -181,7 +181,7 @@ exports.getSessionInfo = async (sessionID:string) => {
* @param {String} sessionID The id of the session
* @return {Promise<void>} Resolves when the session is deleted
*/
exports.deleteSession = async (sessionID:string) => {
const deleteSession = async (sessionID:string) => {
// ensure that the session exists
const session = await db.get(`session:${sessionID}`);
if (session == null) {
@ -210,7 +210,7 @@ exports.deleteSession = async (sessionID:string) => {
* @param {String} groupID The id of the group
* @return {Promise<Object>} The sessioninfos of all sessions of this group
*/
exports.listSessionsOfGroup = async (groupID: string) => {
const listSessionsOfGroup = async (groupID: string) => {
// check that the group exists
const exists = await groupManager.doesGroupExist(groupID);
if (!exists) {
@ -226,7 +226,7 @@ exports.listSessionsOfGroup = async (groupID: string) => {
* @param {String} authorID The id of the author
* @return {Promise<Object>} The sessioninfos of all sessions of this author
*/
exports.listSessionsOfAuthor = async (authorID: string) => {
const listSessionsOfAuthor = async (authorID: string) => {
// check that the author exists
const exists = await authorManager.doesAuthorExist(authorID);
if (!exists) {
@ -251,7 +251,7 @@ const listSessionsWithDBKey = async (dbkey: string) => {
// iterate through the sessions and get the sessioninfos
for (const sessionID of Object.keys(sessions || {})) {
try {
sessions[sessionID] = await exports.getSessionInfo(sessionID);
sessions[sessionID] = await getSessionInfo(sessionID);
} catch (err:any) {
if (err.name === 'apierror') {
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
@ -271,5 +271,15 @@ const listSessionsWithDBKey = async (dbkey: string) => {
* @param {number|string} value
* @return {boolean} If the value is an integer
*/
// @ts-ignore
const isInt = (value:number|string): boolean => (parseFloat(value) === parseInt(value)) && !isNaN(value);
const isInt = (value:any): boolean => (parseFloat(value) === parseInt(value)) && !isNaN(value);
export default {
findAuthorID,
doesSessionExist,
createSession,
getSessionInfo,
deleteSession,
listSessionsOfGroup,
listSessionsOfAuthor,
isInt
}

View file

@ -113,4 +113,4 @@ for (const m of ['get', 'set', 'destroy', 'touch']) {
SessionStore.prototype[m] = util.callbackify(SessionStore.prototype[`_${m}`]);
}
module.exports = SessionStore;
export default SessionStore

View file

@ -1,4 +1,3 @@
'use strict';
/*
* Copyright (c) 2011 RedHog (Egil Möller) <egil.moller@freecode.no>
*
@ -31,44 +30,41 @@ import {pluginInstallPath} from '../../static/js/pluginfw/installer'
const templateCache = new Map();
exports.info = {
export const info: any = {
__output_stack: [],
block_stack: [],
file_stack: [],
args: [],
};
const getCurrentFile = () => exports.info.file_stack[exports.info.file_stack.length - 1];
const getCurrentFile = () => info.file_stack[info.file_stack.length - 1];
exports._init = (b: any, recursive: boolean) => {
exports.info.__output_stack.push(exports.info.__output);
exports.info.__output = b;
export const _init = (b: any, recursive: boolean) => {
info.__output_stack.push(info.__output);
info.__output = b;
};
exports._exit = (b:any, recursive:boolean) => {
exports.info.__output = exports.info.__output_stack.pop();
export const _exit = (b:any, recursive:boolean) => {
info.__output = info.__output_stack.pop();
};
exports.begin_block = (name:string) => {
exports.info.block_stack.push(name);
exports.info.__output_stack.push(exports.info.__output.get());
exports.info.__output.set('');
export const begin_block = (name:string) => {
info.block_stack.push(name);
info.__output_stack.push(info.__output.get());
info.__output.set('');
};
exports.end_block = () => {
const name = exports.info.block_stack.pop();
const renderContext = exports.info.args[exports.info.args.length - 1];
const content = exports.info.__output.get();
exports.info.__output.set(exports.info.__output_stack.pop());
export const end_block = () => {
const name = info.block_stack.pop();
const renderContext = info.args[info.args.length - 1];
const content = info.__output.get();
info.__output.set(info.__output_stack.pop());
const args = {content, renderContext};
hooks.callAll(`eejsBlock_${name}`, args);
exports.info.__output.set(exports.info.__output.get().concat(args.content));
info.__output.set(info.__output.get().concat(args.content));
};
exports.require = (name:string, args:{
e?: Function,
require?: Function,
}, mod:{
export const require2 = (name:string, args: any, mod?:{
filename:string,
paths:string[],
}) => {
@ -77,7 +73,7 @@ exports.require = (name:string, args:{
let basedir = __dirname;
let paths:string[] = [];
if (exports.info.file_stack.length) {
if (info.file_stack.length) {
basedir = path.dirname(getCurrentFile().path);
}
if (mod) {
@ -94,7 +90,14 @@ exports.require = (name:string, args:{
const ejspath = resolve.sync(name, {paths, basedir, extensions: ['.html', '.ejs']});
args.e = exports;
args.e = {
_init: (b:any, recursive:boolean) => _init(b, recursive),
_exit: (b:any, recursive:boolean) => _exit(b, recursive),
begin_block: (name:string) => begin_block(name),
end_block: () => end_block(),
info,
getCurrentFile,
};
args.require = require;
const cache = settings.maxAge !== 0;
@ -104,11 +107,25 @@ exports.require = (name:string, args:{
{filename: ejspath});
if (cache) templateCache.set(ejspath, template);
exports.info.args.push(args);
exports.info.file_stack.push({path: ejspath});
info.args.push(args);
info.file_stack.push({path: ejspath});
const res = template(args);
exports.info.file_stack.pop();
exports.info.args.pop();
info.file_stack.pop();
info.args.pop();
return res;
};
export default {
require: require2,
_init,
_exit,
begin_block,
end_block,
info,
getCurrentFile,
templateCache,
infoStack: info.__output_stack,
blockStack: info.block_stack,
fileStack: info.file_stack,
}

View file

@ -28,7 +28,7 @@ import settings from '../utils/Settings';
import os from 'os';
const hooks = require('../../static/js/pluginfw/hooks');
import util from 'util';
const { checkValidRev } = require('../utils/checkValidRev');
import {checkValidRev} from '../utils/checkValidRev';
const fsp_writeFile = util.promisify(fs.writeFile);
const fsp_unlink = util.promisify(fs.unlink);

View file

@ -22,12 +22,12 @@
import {MapArrayType} from "../types/MapType";
import AttributeMap from '../../static/js/AttributeMap';
const padManager = require('../db/PadManager');
import padManager from '../db/PadManager';
import {checkRep, cloneAText, compose, deserializeOps, follow, identity, inverse, makeAText, makeSplice, moveOpsToNewPool, mutateAttributionLines, mutateTextLines, oldLen, prepareForWire, splitAttributionLines, splitTextLines, unpack} from '../../static/js/Changeset';
import ChatMessage from '../../static/js/ChatMessage';
import AttributePool from '../../static/js/AttributePool';
const AttributeManager = require('../../static/js/AttributeManager');
const authorManager = require('../db/AuthorManager');
import authorManager from '../db/AuthorManager';
import padutils from '../../static/js/pad_utils';
import readOnlyManager from '../db/ReadOnlyManager';
import settings, {
@ -35,13 +35,13 @@ import settings, {
abiwordAvailable,
sofficeAvailable
} from '../utils/Settings';
const securityManager = require('../db/SecurityManager');
import securityManager from '../db/SecurityManager';
const plugins = require('../../static/js/pluginfw/plugin_defs');
import log4js from 'log4js';
const messageLogger = log4js.getLogger('message');
const accessLogger = log4js.getLogger('access');
const hooks = require('../../static/js/pluginfw/hooks');
const stats = require('../stats')
import stats from '../stats';
const assert = require('assert').strict;
import {RateLimiterMemory} from 'rate-limiter-flexible';
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
@ -49,11 +49,12 @@ import {APool, AText, PadAuthor, PadType} from "../types/PadType";
import {ChangeSet} from "../types/ChangeSet";
import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, PadDeleteMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage";
import {Builder} from "../../static/js/Builder";
const webaccess = require('../hooks/express/webaccess');
const { checkValidRev } = require('../utils/checkValidRev');
import webaccess from '../hooks/express/webaccess';
import {checkValidRev} from '../utils/checkValidRev';
import Pad from "../db/Pad";
let rateLimiter:any;
let socketio: any = null;
let _socketio: any = null;
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
@ -66,7 +67,7 @@ const addContextToError = (err:any, pfx:string) => {
return err;
};
exports.socketio = () => {
export const socketio = () => {
// The rate limiter is created in this hook so that restarting the server resets the limiter. The
// settings.commitRateLimiting object is passed directly to the rate limiter so that the limits
// can be dynamically changed during runtime by modifying its properties.
@ -91,10 +92,9 @@ exports.socketio = () => {
* - readonly: Whether the client has read-only access (true) or read/write access (false).
* - rev: The last revision that was sent to the client.
*/
const sessioninfos:MapArrayType<any> = {};
exports.sessioninfos = sessioninfos;
export const sessioninfos:MapArrayType<any> = {};
stats.gauge('totalUsers', () => socketio ? socketio.engine.clientsCount : 0);
stats.gauge('totalUsers', () => _socketio ? _socketio.engine.clientsCount : 0);
stats.gauge('activePads', () => {
const padIds = new Set();
for (const {padId} of Object.values(sessioninfos)) {
@ -149,15 +149,15 @@ 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
* @param socket_io The Socket
*/
exports.setSocketIO = (socket_io:any) => {
socketio = socket_io;
export const setSocketIO = (socket_io:any) => {
_socketio = socket_io;
};
/**
* Handles the connection of a new user
* @param socket the socket.io Socket object for the new connection from the client
*/
exports.handleConnect = (socket:any) => {
export const handleConnect = (socket:any) => {
stats.meter('connects').mark();
// Initialize sessioninfos for this new session
@ -167,22 +167,22 @@ exports.handleConnect = (socket:any) => {
/**
* Kicks all sessions from a pad
*/
exports.kickSessionsFromPad = (padID: string) => {
export const kickSessionsFromPad = (padID: string) => {
if(socketio.sockets == null) return;
if(_socketio.sockets == null) return;
// skip if there is nobody on this pad
if (_getRoomSockets(padID).length === 0) return;
// disconnect everyone from this pad
socketio.in(padID).emit('message', {disconnect: 'deleted'});
_socketio.in(padID).emit('message', {disconnect: 'deleted'});
};
/**
* Handles the disconnection of a user
* @param socket the socket.io Socket object for the client
*/
exports.handleDisconnect = async (socket:any) => {
export const handleDisconnect = async (socket:any) => {
stats.meter('disconnects').mark();
const session = sessioninfos[socket.id];
delete sessioninfos[socket.id];
@ -259,7 +259,7 @@ const handlePadDelete = async (socket: any, padDeleteMessage: PadDeleteMessage)
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
export const handleMessage = async (socket:any, message: ClientVarMessage) => {
const env = process.env.NODE_ENV || 'development';
if (env === 'production') {
@ -315,14 +315,14 @@ exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
const msg = JSON.stringify(message, null, 2);
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
}
const {session: {user} = {}} = socket.client.request as SocketClientRequest;
const {accessStatus, authorID} =
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
if (accessStatus !== 'grant') {
socket.emit('message', {accessStatus});
const result = await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
if (result.accessStatus !== 'grant') {
socket.emit('message', {accessStatus: result.accessStatus});
throw new Error('access denied');
}
const { authorID } = result
if (thisSession.author != null && thisSession.author !== authorID) {
socket.emit('message', {disconnect: 'rejected'});
throw new Error([
@ -443,14 +443,14 @@ const handleSaveRevisionMessage = async (socket:any, message: ClientSaveRevision
* @param msg {Object} the message we're sending
* @param sessionID {string} the socketIO session to which we're sending this message
*/
exports.handleCustomObjectMessage = (msg: CustomMessage, sessionID: string) => {
export const handleCustomObjectMessage = (msg: CustomMessage, sessionID: string) => {
if (msg.data.type === 'CUSTOM') {
if (sessionID) {
// a sessionID is targeted: directly to this sessionID
socketio.sockets.socket(sessionID).emit('message', msg);
_socketio.sockets.socket(sessionID).emit('message', msg);
} else {
// broadcast to all clients on this pad
socketio.sockets.in(msg.data.payload.padId).emit('message', msg);
_socketio.sockets.in(msg.data.payload.padId).emit('message', msg);
}
}
};
@ -461,7 +461,7 @@ exports.handleCustomObjectMessage = (msg: CustomMessage, sessionID: string) => {
* @param padID {Pad} the pad to which we're sending this message
* @param msgString {String} the message we're sending
*/
exports.handleCustomMessage = (padID: string, msgString:string) => {
export const handleCustomMessage = (padID: string, msgString:string) => {
const time = Date.now();
const msg = {
type: 'COLLABROOM',
@ -470,7 +470,7 @@ exports.handleCustomMessage = (padID: string, msgString:string) => {
time,
},
};
socketio.sockets.in(padID).emit('message', msg);
_socketio.sockets.in(padID).emit('message', msg);
};
/**
@ -484,7 +484,7 @@ const handleChatMessage = async (socket:any, message: ChatMessageMessage) => {
// Don't trust the user-supplied values.
chatMessage.time = Date.now();
chatMessage.authorId = authorId;
await exports.sendChatMessageToPadClients(chatMessage, padId);
await sendChatMessageToPadClients(chatMessage, padId);
};
/**
@ -498,16 +498,16 @@ const handleChatMessage = async (socket:any, message: ChatMessageMessage) => {
* @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.
*/
exports.sendChatMessageToPadClients = async (mt: ChatMessage|number, puId: string, text:string|null = null, padId:string|null = null) => {
const sendChatMessageToPadClients = async (mt: ChatMessage|number, puId: string, text:string|null = null, padId:string|null = null) => {
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
padId = mt instanceof ChatMessage ? puId : padId;
const pad = await padManager.getPad(padId, null, message.authorId);
const pad = await padManager.getPad(padId!, null, message.authorId);
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
// pad.appendChatMessage() ignores the displayName property so we don't need to wait for
// authorManager.getAuthorName() to resolve before saving the message to the database.
const promise = pad.appendChatMessage(message);
message.displayName = await authorManager.getAuthorName(message.authorId);
socketio.sockets.in(padId).emit('message', {
message.displayName = await authorManager.getAuthorName(message.authorId!);
_socketio.sockets.in(padId).emit('message', {
type: 'COLLABROOM',
data: {type: 'CHAT_MESSAGE', message},
});
@ -713,7 +713,7 @@ const handleUserChanges = async (socket:any, message: {
socket.emit('message', {type: 'COLLABROOM', data: {type: 'ACCEPT_COMMIT', newRev}});
thisSession.rev = newRev;
if (newRev !== r) thisSession.time = await pad.getRevisionDate(newRev);
await exports.updatePadClients(pad);
await updatePadClients(pad);
} catch (err:any) {
socket.emit('message', {disconnect: 'badChangeset'});
stats.meter('failedChangesets').mark();
@ -724,7 +724,7 @@ const handleUserChanges = async (socket:any, message: {
}
};
exports.updatePadClients = async (pad: PadType) => {
const updatePadClients = async (pad: Pad) => {
// skip this if no-one is on this pad
const roomSockets = _getRoomSockets(pad.id);
if (roomSockets.length === 0) return;
@ -1161,7 +1161,7 @@ const handleChangesetRequest = async (socket:any, {data: {granularity, start, re
* 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
*/
const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, granularity: number) => {
const getChangesetInfo = async (pad: Pad, startNum: number, endNum:number, granularity: number) => {
const headRevision = pad.getHeadRevisionNumber();
// calculate the last full endnum
@ -1192,7 +1192,7 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
getPadLines(pad, startNum - 1),
// Get all needed composite Changesets.
...compositesChangesetNeeded.map(async (item) => {
const changeset = await exports.composePadChangesets(pad, item.start, item.end);
const changeset = await composePadChangesets(pad, item.start, item.end);
composedChangesets[`${item.start}/${item.end}`] = changeset;
}),
// Get all needed revision Dates.
@ -1238,7 +1238,7 @@ const getChangesetInfo = async (pad: PadType, startNum: number, endNum:number, g
* 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
*/
const getPadLines = async (pad: PadType, revNum: number) => {
const getPadLines = async (pad: Pad, revNum: number) => {
// get the atext
let atext;
@ -1258,7 +1258,7 @@ const getPadLines = async (pad: PadType, revNum: number) => {
* 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
*/
exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: number) => {
export const composePadChangesets = async (pad: Pad, startNum: number, endNum: number) => {
// fetch all changesets we need
const headNum = pad.getHeadRevisionNumber();
endNum = Math.min(endNum, headNum + 1);
@ -1297,7 +1297,7 @@ exports.composePadChangesets = async (pad: PadType, startNum: number, endNum: nu
};
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
// it does here, but synchronously to avoid a race condition. This code will have to change when
// we update to socket.io v3.
@ -1313,14 +1313,14 @@ const _getRoomSockets = (padID: string) => {
/**
* Get the number of users in a pad
*/
exports.padUsersCount = (padID:string) => ({
export const padUsersCount = (padID:string) => ({
padUsersCount: _getRoomSockets(padID).length,
});
/**
* Get the list of users in a pad
*/
exports.padUsers = async (padID: string) => {
export const padUsers = async (padID: string) => {
const padUsers:PadAuthor[] = [];
// iterate over all clients (in parallel)
@ -1340,4 +1340,21 @@ exports.padUsers = async (padID: string) => {
return {padUsers};
};
exports.sessioninfos = sessioninfos;
export default {
sessioninfos,
handleGetChatMessages,
sendChatMessageToPadClients,
handleSuggestUserName,
updatePadClients,
handleUserInfoUpdate,
handleUserChanges,
handleCustomMessage,
handleClientReady,
handleChangesetRequest,
getChangesetInfo,
composePadChangesets,
getPadLines,
padUsersCount,
padUsers,
kickSessionsFromPad
}

View file

@ -24,7 +24,7 @@ import {MapArrayType} from "../types/MapType";
import {SocketModule} from "../types/SocketModule";
import log4js from 'log4js';
import settings from '../utils/Settings';
const stats = require('../../node/stats')
import stats from '../../node/stats';
const logger = log4js.getLogger('socket.io');

View file

@ -11,11 +11,11 @@ import expressSession, {Store} from 'express-session';
import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks');
import log4js from 'log4js';
const SessionStore = require('../db/SessionStore');
import SessionStore from '../db/SessionStore';
import settings, {getEpVersion, getGitCommit} from '../utils/Settings';
const stats = require('../stats')
import stats from '../stats';
import util from 'util';
const webaccess = require('./express/webaccess');
import webaccess from './express/webaccess';
import SecretRotator from '../security/SecretRotator';

View file

@ -1,14 +1,14 @@
'use strict';
import path from 'node:path';
const eejs = require('../../eejs')
import eejs from '../../eejs';
import fs from 'node:fs';
const fsp = fs.promises;
const toolbar = require('../../utils/toolbar');
const hooks = require('../../../static/js/pluginfw/hooks');
import settings, {getEpVersion} from '../../utils/Settings';
import util from 'node:util';
const webaccess = require('./webaccess');
import webaccess from './webaccess';
const plugins = require('../../../static/js/pluginfw/plugin_defs');
import {build, buildSync} from 'esbuild'

View file

@ -21,7 +21,7 @@ const aCallFirst0 =
// @ts-ignore
async (hookName: string, context:any, pred = null) => (await aCallFirst(hookName, context, pred))[0];
exports.normalizeAuthzLevel = (level: string|boolean) => {
export const normalizeAuthzLevel = (level: string|boolean) => {
if (!level) return false;
switch (level) {
case true:
@ -36,18 +36,18 @@ exports.normalizeAuthzLevel = (level: string|boolean) => {
return false;
};
exports.userCanModify = (padId: string, req: SocketClientRequest) => {
export const userCanModify = (padId: string, req: SocketClientRequest) => {
if (readOnlyManager.isReadOnlyId(padId)) return false;
if (!settings.requireAuthentication) return true;
const {session: {user} = {}} = req;
if (!user || user.readOnly) return false;
assert(user.padAuthorizations); // This is populated even if !settings.requireAuthorization.
const level = exports.normalizeAuthzLevel(user.padAuthorizations[padId]);
const level = normalizeAuthzLevel(user.padAuthorizations[padId]);
return level && level !== 'readOnly';
};
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
exports.authnFailureDelayMs = 1000;
const authnFailureDelayMs = 1000;
const staticResources = [
/^\/padbootstrap-[a-zA-Z0-9]+\.min\.js$/,
@ -55,7 +55,7 @@ const staticResources = [
/^\/manifest.json$/
]
const checkAccess = async (req:any, res:any, next: Function) => {
const _checkAccess = async (req:any, res:any, next: Function) => {
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth');
for (const staticResource of staticResources) {
if (req.path.match(staticResource)) {
@ -106,7 +106,7 @@ const checkAccess = async (req:any, res:any, next: Function) => {
// authentication is checked and once after (if settings.requireAuthorization is true).
const authorize = async () => {
const grant = async (level: string|false) => {
level = exports.normalizeAuthzLevel(level);
level = normalizeAuthzLevel(level);
if (!level) return false;
const user = req.session.user;
if (user == null) return true; // This will happen if authentication is not required.
@ -186,7 +186,7 @@ const checkAccess = async (req:any, res:any, next: Function) => {
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
}
// Delay the error response for 1s to slow down brute force attacks.
await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
await new Promise((resolve) => setTimeout(resolve, authnFailureDelayMs));
res.status(401).send('Authentication Required');
return;
}
@ -230,6 +230,12 @@ const checkAccess = async (req:any, res:any, next: Function) => {
* Express middleware to authenticate the user and check authorization. Must be installed after the
* express-session middleware.
*/
exports.checkAccess = (req:any, res:any, next:Function) => {
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
const checkAccess = (req:any, res:any, next:Function) => {
_checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
};
export default {
checkAccess,
normalizeAuthzLevel,
userCanModify
}

View file

@ -2,19 +2,19 @@
import {DeriveModel} from "../types/DeriveModel";
import {LegacyParams} from "../types/LegacyParams";
import {Buffer} from 'node:buffer'
import crypto from './crypto'
const {Buffer} = require('buffer');
const crypto = require('./crypto');
const db = require('../db/DB');
const log4js = require('log4js');
import db from '../db/DB';
import log4js from "log4js";
class Kdf {
async generateParams(): Promise<{ salt: string; digest: string; keyLen: number; secret: string }> { throw new Error('not implemented'); }
async derive(params: DeriveModel, info: any) { throw new Error('not implemented'); }
async derive(params: DeriveModel, info: any): Promise<string> { throw new Error('not implemented'); }
}
class LegacyStaticSecret extends Kdf {
async derive(params:any, info:any) { return params; }
async derive(params:any, info:any) : Promise<string> { return params; }
}
class Hkdf extends Kdf {

View file

@ -1,15 +1,18 @@
'use strict';
const crypto = require('crypto');
const util = require('util');
import crypto from 'node:crypto'
import util from 'node:util'
/**
* Promisified version of Node.js's crypto.hkdf.
*/
exports.hkdf = util.promisify(crypto.hkdf);
const hkdf = util.promisify(crypto.hkdf);
/**
* Promisified version of Node.js's crypto.randomBytes
*/
exports.randomBytes = util.promisify(crypto.randomBytes);
const randomBytes = util.promisify(crypto.randomBytes);
export default {
hkdf,
randomBytes
}

View file

@ -79,7 +79,7 @@ const hooks = require('../static/js/pluginfw/hooks');
const pluginDefs = require('../static/js/pluginfw/plugin_defs');
const plugins = require('../static/js/pluginfw/plugins');
import {Gate} from './utils/promises';
const stats = require('./stats')
import stats from './stats'
const logger = log4js.getLogger('server');

View file

@ -2,9 +2,9 @@
const measured = require('measured-core');
module.exports = measured.createCollection();
export default measured.createCollection();
// @ts-ignore
module.exports.shutdown = async (hookName, context) => {
export const shutdown = async (hookName, context) => {
module.exports.end();
};
};

View file

@ -1,5 +1,5 @@
export type UserSettingsObject = {
canCreate: boolean,
canCreate?: boolean,
readOnly: boolean,
padAuthorizations: any
}

View file

@ -4,10 +4,10 @@ import {AChangeSet} from "../types/PadType";
import {Revision} from "../types/Revision";
import {timesLimit, firstSatisfies} from './promises';
const padManager = require('ep_etherpad-lite/node/db/PadManager');
const db = require('ep_etherpad-lite/node/db/DB');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padMessageHandler = require('ep_etherpad-lite/node/handler/PadMessageHandler');
import padManager from 'ep_etherpad-lite/node/db/PadManager';
import db from 'ep_etherpad-lite/node/db/DB';
import {applyToAText, makeAText} from 'ep_etherpad-lite/static/js/Changeset';
import padMessageHandler from 'ep_etherpad-lite/node/handler/PadMessageHandler';
import log4js from 'log4js';
const logger = log4js.getLogger('cleanup');
@ -88,10 +88,10 @@ export const deleteRevisions = async (padId: string, keepRevisions: number): Pro
}
await db.set(`pad:${padId}`, padContent);
let newAText = Changeset.makeAText('\n');
let newAText = makeAText('\n');
let pool = pad.apool()
newAText = Changeset.applyToAText(changeset, newAText, pool);
newAText = applyToAText(changeset as string, newAText, pool);
const revision = await createRevision(
changeset,
@ -110,7 +110,7 @@ export const deleteRevisions = async (padId: string, keepRevisions: number): Pro
const rev = i + cleanupUntilRevision + 1
const newRev = rev - cleanupUntilRevision;
newAText = Changeset.applyToAText(revisions[rev].changeset, newAText, pool);
newAText = applyToAText(revisions[rev].changeset as string, newAText, pool);
const revision = await createRevision(
revisions[rev].changeset,
@ -152,7 +152,7 @@ export const checkTodos = async () => {
const revisionDate = await pad.getRevisionDate(pad.getHeadRevisionNumber())
if (pad.head < settings.minHead || padMessageHandler.padUsersCount(padId) > 0 || Date.now() < revisionDate + settings.minAge) {
if (pad.head < settings.minHead || padMessageHandler.padUsersCount(padId).padUsersCount > 0 || Date.now() < revisionDate + settings.minAge) {
return
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
const Stream = require('./Stream');
import Stream from './Stream';
const assert = require('assert').strict;
const authorManager = require('../db/AuthorManager');
const hooks = require('../../static/js/pluginfw/hooks');

View file

@ -22,13 +22,12 @@
import AttributeMap from '../../static/js/AttributeMap';
import AttributePool from "../../static/js/AttributePool";
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
const { checkValidRev } = require('./checkValidRev');
import {checkValidRev} from './checkValidRev';
/*
* This method seems unused in core and no plugins depend on it
*/
exports.getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any; atext: any; pool: any; }, revNum: undefined) => {
const _analyzeLine = exports._analyzeLine;
export const getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any; atext: any; pool: any; }, revNum: undefined) => {
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = splitAttributionLines(atext.attribs, atext.text);
@ -38,7 +37,7 @@ exports.getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any;
for (let i = 0; i < textLines.length; i++) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel) {
const numSpaces = line.listLevel * 2 - 1;
const numSpaces = (line.listLevel as number) * 2 - 1;
const bullet = '*';
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
} else {
@ -49,10 +48,14 @@ exports.getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any;
return pieces.join('');
};
type LineModel = {
[id:string]:string|number|LineModel
listLevel?: number;
text?: string
listTypeName?: string;
start?: string;
aline?: string;
}
exports._analyzeLine = (text:string, aline: string, apool: AttributePool) => {
export const _analyzeLine = (text:string, aline: string, apool: AttributePool) => {
const line: LineModel = {};
// identify list
@ -88,5 +91,5 @@ exports._analyzeLine = (text:string, aline: string, apool: AttributePool) => {
};
exports._encodeWhitespace =
export const _encodeWhitespace =
(s:string) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);

View file

@ -21,17 +21,18 @@ import {MapArrayType} from "../types/MapType";
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
const attributes = require('../../static/js/attributes');
const padManager = require('../db/PadManager');
const _ = require('underscore');
import _ from 'underscore';
const Security = require('../../static/js/security');
const hooks = require('../../static/js/pluginfw/hooks');
const eejs = require('../eejs');
import eejs from '../eejs';
const _analyzeLine = require('./ExportHelper')._analyzeLine;
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
import padutils from "../../static/js/pad_utils";
import {StringIterator} from "../../static/js/StringIterator";
import {StringAssembler} from "../../static/js/StringAssembler";
import Pad from "../db/Pad";
const getPadHTML = async (pad: PadType, revNum: string) => {
export const getPadHTML = async (pad: Pad, revNum: number) => {
let atext = pad.atext;
// fetch revision atext
@ -43,7 +44,7 @@ const getPadHTML = async (pad: PadType, revNum: string) => {
return await getHTMLFromAtext(pad, atext);
};
const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string[]) => {
export const getHTMLFromAtext = async (pad: Pad, atext: AText, authorColors?: MapArrayType<string>) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = splitAttributionLines(atext.attribs, atext.text);
@ -476,7 +477,7 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
return pieces.join('');
};
exports.getPadHTMLDocument = async (padId: string, revNum: string, readOnlyId: number) => {
export const getPadHTMLDocument = async (padId: string, revNum: number, readOnlyId: number) => {
const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export
@ -548,5 +549,11 @@ const _processSpaces = (s: string) => {
return parts.join('');
};
exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;
export default {
getPadHTML,
getHTMLFromAtext,
getPadHTMLDocument,
_processSpaces,
_analyzeLine,
_encodeWhitespace,
}

View file

@ -20,17 +20,18 @@
*/
import {AText, PadType} from "../types/PadType";
import {MapType} from "../types/MapType";
import {MapArrayType, MapType} from "../types/MapType";
import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
import {StringIterator} from "../../static/js/StringIterator";
import {StringAssembler} from "../../static/js/StringAssembler";
const attributes = require('../../static/js/attributes');
const padManager = require('../db/PadManager');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
import padManager from '../db/PadManager';
import {_analyzeLine} from "./ExportHelper";
import Pad from "../db/Pad";
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
const getPadTXT = async (pad: PadType, revNum: string) => {
const getPadTXT = async (pad: Pad, revNum: number) => {
let atext = pad.atext;
if (revNum !== undefined) {
@ -44,7 +45,7 @@ const getPadTXT = async (pad: PadType, revNum: string) => {
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
const getTXTFromAtext = (pad: Pad, atext: AText, authorColors?:string) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = splitAttributionLines(atext.attribs, atext.text);
@ -195,12 +196,12 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
const listNumbers:MapType = {};
const listNumbers:MapArrayType<number> = {};
let prevListLevel;
for (let i = 0; i < textLines.length; i++) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
let lineContent = getLineTXT(line.text, line.aline);
let lineContent = getLineTXT(line.text!, line.aline);
if (line.listTypeName === 'bullet') {
lineContent = `* ${lineContent}`; // add a bullet
@ -213,7 +214,7 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
}
}
if (line.listLevel > 0) {
if (line.listLevel && line.listLevel > 0) {
for (let j = line.listLevel - 1; j >= 0; j--) {
pieces.push('\t'); // tab indent list numbers..
if (!listNumbers[line.listLevel]) {
@ -235,12 +236,11 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
* To handle going back to 2.1 when prevListLevel is lower number
* than current line.listLevel then reset the object value
*/
if (line.listLevel < prevListLevel) {
if (prevListLevel && line.listLevel < prevListLevel) {
delete listNumbers[prevListLevel];
}
// @ts-ignore
listNumbers[line.listLevel]++;
listNumbers[line.listLevel.toString()]++;
if (line.listLevel > 1) {
let x = 1;
while (x <= line.listLevel - 1) {
@ -263,9 +263,13 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
return pieces.join('');
};
exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = async (padId:string, revNum:string) => {
export const getPadTXTDocument = async (padId:string, revNum: number) => {
const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum);
};
export default {
getPadTXT,
getTXTFromAtext,
getPadTXTDocument,
}

View file

@ -20,7 +20,7 @@ import {APool} from "../types/PadType";
import AttributePool from '../../static/js/AttributePool';
const {Pad} = require('../db/Pad');
const Stream = require('./Stream');
import Stream from './Stream';
const authorManager = require('../db/AuthorManager');
const db = require('../db/DB');
const hooks = require('../../static/js/pluginfw/hooks');

View file

@ -19,13 +19,13 @@ import log4js from 'log4js';
import {deserializeOps} from '../../static/js/Changeset';
const contentcollector = require('../../static/js/contentcollector');
import jsdom from 'jsdom';
import {PadType} from "../types/PadType";
import {Builder} from "../../static/js/Builder";
import Pad from "../db/Pad";
const apiLogger = log4js.getLogger('ImportHtml');
let processor:any;
exports.setPadHTML = async (pad: PadType, html:string, authorId = '') => {
export const setPadHTML = async (pad: Pad, html:string, authorId = '') => {
if (processor == null) {
const [{rehype}, {default: minifyWhitespace}] =
await Promise.all([import('rehype'), import('rehype-minify-whitespace')]);
@ -93,3 +93,7 @@ exports.setPadHTML = async (pad: PadType, html:string, authorId = '') => {
await pad.setText('\n', authorId);
await pad.appendRevision(theChangeset, authorId);
};
export default {
setPadHTML,
}

View file

@ -136,4 +136,4 @@ class Stream {
[Symbol.iterator]() { return this._iter; }
}
module.exports = Stream;
export default Stream;

View file

@ -1,10 +1,10 @@
'use strict';
const CustomError = require('../utils/customError');
import CustomError from '../utils/customError';
// checks if a rev is a legal number
// pre-condition is that `rev` is not undefined
const checkValidRev = (rev: number|string) => {
export const checkValidRev = (rev: number|string) => {
if (typeof rev !== 'number') {
rev = parseInt(rev, 10);
}
@ -28,7 +28,4 @@ const checkValidRev = (rev: number|string) => {
};
// checks if a number is an int
const isInt = (value:number) => (parseFloat(String(value)) === parseInt(String(value), 10)) && !isNaN(value);
exports.isInt = isInt;
exports.checkValidRev = checkValidRev;
export const isInt = (value:number) => (parseFloat(String(value)) === parseInt(String(value), 10)) && !isNaN(value);

View file

@ -1,4 +1,3 @@
'use strict';
/**
* CustomError
*
@ -21,4 +20,4 @@ class CustomError extends Error {
}
}
module.exports = CustomError;
export default CustomError

View file

@ -11,17 +11,18 @@ import {numToString} from "../../static/js/ChangesetUtils";
import Op from "../../static/js/Op";
import {StringAssembler} from "../../static/js/StringAssembler";
const attributes = require('../../static/js/attributes');
const exportHtml = require('./ExportHtml');
import exportHtml from './ExportHtml';
import Pad from "../db/Pad";
class PadDiff {
private readonly _pad: PadType;
private readonly _pad: Pad;
private readonly _fromRev: string;
private readonly _toRev: string;
private _html: any;
public _authors: any[];
private self: PadDiff | undefined
constructor(pad: PadType, fromRev:string, toRev:string) {
constructor(pad: Pad, fromRev:number, toRev:number) {
// check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad');
@ -466,4 +467,4 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
};
// export the constructor
module.exports = PadDiff;
export default PadDiff

View file

@ -30,6 +30,13 @@
}
],
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.62.0",
"@opentelemetry/resources": "^2.0.1",
"@opentelemetry/sdk-metrics": "^2.0.1",
"@opentelemetry/sdk-node": "^0.203.0",
"@opentelemetry/sdk-trace-base": "^2.0.1",
"@opentelemetry/semantic-conventions": "^1.36.0",
"async": "^3.2.6",
"axios": "^1.11.0",
"cookie-parser": "^1.4.7",

View file

@ -1,7 +1,8 @@
'use strict';
const Stream = require('../../../node/utils/Stream');
import Stream from '../../../node/utils/Stream';
import {strict} from "assert";
import {it,describe,} from 'vitest'
class DemoIterable {
private value: number;
@ -24,7 +25,7 @@ class DemoIterable {
const alreadyCompleted = this.completed();
this.errs.push(err);
if (alreadyCompleted) throw err; // Mimic standard generator objects.
throw err;
return err
}
return(ret: number) {
@ -112,11 +113,11 @@ describe(__filename, function () {
it('throw is propagated', async function () {
const underlying = new DemoIterable();
const s = new Stream(underlying);
const s = new Stream(underlying satisfies Iterable<any, any, any>);
const iter = s[Symbol.iterator]();
strict.deepEqual(iter.next(), {value: 0, done: false});
const err = new Error('injected');
strict.throws(() => iter.throw(err), err);
strict.throws(() => iter!.throw!(err), err);
strict.equal(underlying.errs[0], err);
});
@ -125,7 +126,7 @@ describe(__filename, function () {
const s = new Stream(underlying);
const iter = s[Symbol.iterator]();
strict.deepEqual(iter.next(), {value: 0, done: false});
strict.deepEqual(iter.return(42), {value: 42, done: true});
strict.deepEqual(iter.return!(42), {value: 42, done: true});
strict.equal(underlying.rets[0], 42);
});
});
@ -225,7 +226,7 @@ describe(__filename, function () {
strict.equal(lastYield, 'promise of 2');
strict.equal(await nextp, 0);
await strict.rejects(iter.next().value, err);
iter.return();
iter.return!();
});
it('batched Promise rejections are unsuppressed when iteration completes', async function () {
@ -243,7 +244,7 @@ describe(__filename, function () {
const iter = s[Symbol.iterator]();
strict.equal(await iter.next().value, 0);
strict.equal(lastYield, 'promise of 2');
await assertUnhandledRejection(() => iter.return(), err);
await assertUnhandledRejection(() => iter.return!(), err);
});
});
@ -319,7 +320,7 @@ describe(__filename, function () {
strict.equal(lastYield, 'promise of 2');
strict.equal(await nextp, 0);
await strict.rejects(iter.next().value, err);
iter.return();
iter.return!();
});
it('buffered Promise rejections are unsuppressed when iteration completes', async function () {
@ -337,7 +338,7 @@ describe(__filename, function () {
const iter = s[Symbol.iterator]();
strict.equal(await iter.next().value, 0);
strict.equal(lastYield, 'promise of 2');
await assertUnhandledRejection(() => iter.return(), err);
await assertUnhandledRejection(() => iter!.return!(), err);
});
});