diff --git a/node_modules/ep_etherpad-lite b/node_modules/ep_etherpad-lite
deleted file mode 120000
index 5cd551cf269..00000000000
--- a/node_modules/ep_etherpad-lite
+++ /dev/null
@@ -1 +0,0 @@
-../src
\ No newline at end of file
diff --git a/src/.babelrc b/src/.babelrc
new file mode 100644
index 00000000000..1320b9a3272
--- /dev/null
+++ b/src/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["@babel/preset-env"]
+}
diff --git a/src/.eslintrc.cjs b/src/.eslintrc.cjs
index 95c9efa07f2..0930b688898 100644
--- a/src/.eslintrc.cjs
+++ b/src/.eslintrc.cjs
@@ -1,10 +1,6 @@
+import "eslint-config-etherpad/patch/modern-module-resolution";
'use strict';
-
-// This is a workaround for https://github.com/eslint/eslint/issues/3458
-require('eslint-config-etherpad/patch/modern-module-resolution');
-
-module.exports = {
- ignorePatterns: [
+export const ignorePatterns = [
'/static/js/admin/jquery.autosize.js',
'/static/js/admin/minify.json.js',
'/static/js/vendors/browser.js',
@@ -14,128 +10,132 @@ module.exports = {
'/static/js/vendors/jquery.js',
'/static/js/vendors/nice-select.js',
'/tests/frontend/lib/',
- ],
- overrides: [
+];
+export const overrides = [
{
- files: [
- '**/.eslintrc.*',
- ],
- extends: 'etherpad/node',
+ files: [
+ '**/.eslintrc.*',
+ ],
+ extends: 'etherpad/node',
},
{
- files: [
- '**/*',
- ],
- excludedFiles: [
- '**/.eslintrc.*',
- 'tests/frontend/**/*',
- ],
- extends: 'etherpad/node',
+ files: [
+ '**/*',
+ ],
+ excludedFiles: [
+ '**/.eslintrc.*',
+ 'tests/frontend/**/*',
+ ],
+ extends: 'etherpad/node',
},
{
- files: [
- 'static/**/*',
- 'tests/frontend/helper.js',
- 'tests/frontend/helper/**/*',
- ],
- excludedFiles: [
- '**/.eslintrc.*',
- ],
- extends: 'etherpad/browser',
- env: {
- 'shared-node-browser': true,
- },
- overrides: [
- {
- files: [
+ files: [
+ 'static/**/*',
+ 'tests/frontend/helper.js',
'tests/frontend/helper/**/*',
- ],
- globals: {
- helper: 'readonly',
- },
+ ],
+ excludedFiles: [
+ '**/.eslintrc.*',
+ ],
+ extends: 'etherpad/browser',
+ env: {
+ 'shared-node-browser': true,
},
- ],
- },
- {
- files: [
- 'tests/**/*',
- ],
- excludedFiles: [
- '**/.eslintrc.*',
- 'tests/frontend/cypress/**/*',
- 'tests/frontend/helper.js',
- 'tests/frontend/helper/**/*',
- 'tests/frontend/travis/**/*',
- 'tests/ratelimit/**/*',
- ],
- extends: 'etherpad/tests',
- rules: {
- 'mocha/no-exports': 'off',
- 'mocha/no-top-level-hooks': 'off',
- },
+ overrides: [
+ {
+ files: [
+ 'tests/frontend/helper/**/*',
+ ],
+ globals: {
+ helper: 'readonly',
+ },
+ },
+ ],
},
{
- files: [
- 'tests/backend/**/*',
- ],
- excludedFiles: [
- '**/.eslintrc.*',
- ],
- extends: 'etherpad/tests/backend',
- overrides: [
- {
- files: [
- 'tests/backend/**/*',
- ],
- excludedFiles: [
- 'tests/backend/specs/**/*',
- ],
- rules: {
+ files: [
+ 'tests/**/*',
+ ],
+ excludedFiles: [
+ '**/.eslintrc.*',
+ 'tests/frontend/cypress/**/*',
+ 'tests/frontend/helper.js',
+ 'tests/frontend/helper/**/*',
+ 'tests/frontend/travis/**/*',
+ 'tests/ratelimit/**/*',
+ ],
+ extends: 'etherpad/tests',
+ rules: {
'mocha/no-exports': 'off',
'mocha/no-top-level-hooks': 'off',
- },
},
- ],
},
{
- files: [
- 'tests/frontend/**/*',
- ],
- excludedFiles: [
- '**/.eslintrc.*',
- 'tests/frontend/cypress/**/*',
- 'tests/frontend/helper.js',
- 'tests/frontend/helper/**/*',
- 'tests/frontend/travis/**/*',
- ],
- extends: 'etherpad/tests/frontend',
- overrides: [
- {
- files: [
+ files: [
+ 'tests/backend/**/*',
+ ],
+ excludedFiles: [
+ '**/.eslintrc.*',
+ ],
+ extends: 'etherpad/tests/backend',
+ overrides: [
+ {
+ files: [
+ 'tests/backend/**/*',
+ ],
+ excludedFiles: [
+ 'tests/backend/specs/**/*',
+ ],
+ rules: {
+ 'mocha/no-exports': 'off',
+ 'mocha/no-top-level-hooks': 'off',
+ },
+ },
+ ],
+ },
+ {
+ files: [
'tests/frontend/**/*',
- ],
- excludedFiles: [
- 'tests/frontend/specs/**/*',
- ],
- rules: {
- 'mocha/no-exports': 'off',
- 'mocha/no-top-level-hooks': 'off',
- },
- },
- ],
+ ],
+ excludedFiles: [
+ '**/.eslintrc.*',
+ 'tests/frontend/cypress/**/*',
+ 'tests/frontend/helper.js',
+ 'tests/frontend/helper/**/*',
+ 'tests/frontend/travis/**/*',
+ ],
+ extends: 'etherpad/tests/frontend',
+ overrides: [
+ {
+ files: [
+ 'tests/frontend/**/*',
+ ],
+ excludedFiles: [
+ 'tests/frontend/specs/**/*',
+ ],
+ rules: {
+ 'mocha/no-exports': 'off',
+ 'mocha/no-top-level-hooks': 'off',
+ },
+ },
+ ],
},
{
- files: [
- 'tests/frontend/cypress/**/*',
- ],
- extends: 'etherpad/tests/cypress',
+ files: [
+ 'tests/frontend/cypress/**/*',
+ ],
+ extends: 'etherpad/tests/cypress',
},
{
- files: [
- 'tests/frontend/travis/**/*',
- ],
- extends: 'etherpad/node',
+ files: [
+ 'tests/frontend/travis/**/*',
+ ],
+ extends: 'etherpad/node',
},
- ],
- root: true,
+];
+export const root = true;
+export default {
+ ignorePatterns,
+ overrides,
+ root
};
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 00000000000..81cd8b481cc
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+dist/
+node_modules
diff --git a/src/bin/buildDebian.sh b/src/bin/buildDebian.sh
old mode 100755
new mode 100644
diff --git a/src/bin/buildForWindows.sh b/src/bin/buildForWindows.sh
old mode 100755
new mode 100644
diff --git a/src/bin/cleanRun.sh b/src/bin/cleanRun.sh
old mode 100755
new mode 100644
diff --git a/src/bin/createRelease.sh b/src/bin/createRelease.sh
old mode 100755
new mode 100644
diff --git a/src/bin/deb-src/DEBIAN/postinst b/src/bin/deb-src/DEBIAN/postinst
old mode 100755
new mode 100644
diff --git a/src/bin/deb-src/DEBIAN/preinst b/src/bin/deb-src/DEBIAN/preinst
old mode 100755
new mode 100644
diff --git a/src/bin/deb-src/DEBIAN/prerm b/src/bin/deb-src/DEBIAN/prerm
old mode 100755
new mode 100644
diff --git a/src/bin/debugRun.sh b/src/bin/debugRun.sh
old mode 100755
new mode 100644
diff --git a/src/bin/etherpad-healthcheck b/src/bin/etherpad-healthcheck
old mode 100755
new mode 100644
diff --git a/src/bin/fastRun.sh b/src/bin/fastRun.sh
old mode 100755
new mode 100644
diff --git a/src/bin/installDeps.sh b/src/bin/installDeps.sh
old mode 100755
new mode 100644
diff --git a/src/bin/nsis/brand.ico b/src/bin/nsis/brand.ico
index cb7d6a01979..e69de29bb2d 100644
Binary files a/src/bin/nsis/brand.ico and b/src/bin/nsis/brand.ico differ
diff --git a/src/bin/plugins/README.md b/src/bin/plugins/README.md
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/checkPlugin.js b/src/bin/plugins/checkPlugin.js
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/getCorePlugins.sh b/src/bin/plugins/getCorePlugins.sh
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/lib/README.md b/src/bin/plugins/lib/README.md
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/lib/gitignore b/src/bin/plugins/lib/gitignore
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/listOfficialPlugins b/src/bin/plugins/listOfficialPlugins
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/reTestAllPlugins.sh b/src/bin/plugins/reTestAllPlugins.sh
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/updateAllPluginsScript.sh b/src/bin/plugins/updateAllPluginsScript.sh
old mode 100755
new mode 100644
diff --git a/src/bin/plugins/updateCorePlugins.sh b/src/bin/plugins/updateCorePlugins.sh
old mode 100755
new mode 100644
diff --git a/src/bin/run.sh b/src/bin/run.sh
old mode 100755
new mode 100644
index b655e96354a..0e681bdf67b
--- a/src/bin/run.sh
+++ b/src/bin/run.sh
@@ -32,4 +32,4 @@ src/bin/installDeps.sh "$@" || exit 1
# Move to the node folder and start
log "Starting Etherpad..."
-exec node src/node/server.js "$@"
+exec node src/dist/node/server.js "$@"
diff --git a/src/bin/safeRun.sh b/src/bin/safeRun.sh
old mode 100755
new mode 100644
diff --git a/src/bin/updatePlugins.sh b/src/bin/updatePlugins.sh
old mode 100755
new mode 100644
diff --git a/src/node/db/API.js b/src/node/db/API.ts
similarity index 81%
rename from src/node/db/API.js
rename to src/node/db/API.ts
index 9b2ecadc74a..5f07339085b 100644
--- a/src/node/db/API.js
+++ b/src/node/db/API.ts
@@ -19,59 +19,49 @@
* limitations under the License.
*/
-const Changeset = require('../../static/js/Changeset');
-const ChatMessage = require('../../static/js/ChatMessage');
-const CustomError = require('../utils/customError');
-const padManager = require('./PadManager');
-const padMessageHandler = require('../handler/PadMessageHandler');
-const readOnlyManager = require('./ReadOnlyManager');
-const groupManager = require('./GroupManager');
-const authorManager = require('./AuthorManager');
-const sessionManager = require('./SessionManager');
-const exportHtml = require('../utils/ExportHtml');
-const exportTxt = require('../utils/ExportTxt');
-const importHtml = require('../utils/ImportHtml');
+import {builder, deserializeOps} from '../../static/js/Changeset';
+import ChatMessage from '../../static/js/ChatMessage';
+import {CustomError} from '../utils/customError';
+import {doesPadExist, getPad, isValidPadId, listAllPads} from './PadManager';
+import {
+ handleCustomMessage,
+ sendChatMessageToPadClients,
+ sessioninfos,
+ updatePadClients
+} from '../handler/PadMessageHandler';
+import {getPadId, getReadOnlyId} from './ReadOnlyManager';
+import {
+ createGroup,
+ createGroupIfNotExistsFor,
+ createGroupPad,
+ deleteGroup,
+ listAllGroups,
+ listPads
+} from './GroupManager';
+import {createAuthor, createAuthorIfNotExistsFor, getAuthorName, listPadsOfAuthor} from './AuthorManager';
+import {} from './SessionManager';
+import {getTXTFromAtext} from '../utils/ExportTxt';
+import {setPadHTML} from '../utils/ImportHtml';
const cleanText = require('./Pad').cleanText;
-const PadDiff = require('../utils/padDiff');
+import {PadDiff} from '../utils/padDiff';
+import {getPadHTMLDocument} from "../utils/ExportHtml";
/* ********************
* GROUP FUNCTIONS ****
******************** */
-exports.listAllGroups = groupManager.listAllGroups;
-exports.createGroup = groupManager.createGroup;
-exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
-exports.deleteGroup = groupManager.deleteGroup;
-exports.listPads = groupManager.listPads;
-exports.createGroupPad = groupManager.createGroupPad;
-
/* ********************
* PADLIST FUNCTION ***
******************** */
-exports.listAllPads = padManager.listAllPads;
-
/* ********************
* AUTHOR FUNCTIONS ***
******************** */
-exports.createAuthor = authorManager.createAuthor;
-exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
-exports.getAuthorName = authorManager.getAuthorName;
-exports.listPadsOfAuthor = authorManager.listPadsOfAuthor;
-exports.padUsers = padMessageHandler.padUsers;
-exports.padUsersCount = padMessageHandler.padUsersCount;
-
/* ********************
* SESSION FUNCTIONS **
******************** */
-exports.createSession = sessionManager.createSession;
-exports.deleteSession = sessionManager.deleteSession;
-exports.getSessionInfo = sessionManager.getSessionInfo;
-exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
-exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
-
/* ***********************
* PAD CONTENT FUNCTIONS *
*********************** */
@@ -103,7 +93,7 @@ Example returns:
}
*/
-exports.getAttributePool = async (padID) => {
+export const getAttributePool = async (padID: string) => {
const pad = await getPadSafe(padID, true);
return {pool: pad.pool};
};
@@ -121,7 +111,7 @@ Example returns:
}
*/
-exports.getRevisionChangeset = async (padID, rev) => {
+export const getRevisionChangeset = async (padID, rev) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -154,7 +144,7 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getText = async (padID, rev) => {
+export const getText = async (padID, rev) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -179,7 +169,7 @@ exports.getText = async (padID, rev) => {
}
// the client wants the latest text, lets return it to him
- const text = exportTxt.getTXTFromAtext(pad, pad.atext);
+ const text = getTXTFromAtext(pad, pad.atext);
return {text};
};
@@ -192,7 +182,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
-exports.setText = async (padID, text, authorId = '') => {
+export const setText = async (padID, text, authorId = '') => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -202,7 +192,7 @@ exports.setText = async (padID, text, authorId = '') => {
const pad = await getPadSafe(padID, true);
await pad.setText(text, authorId);
- await padMessageHandler.updatePadClients(pad);
+ await updatePadClients(pad);
};
/**
@@ -214,7 +204,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
-exports.appendText = async (padID, text, authorId = '') => {
+export const appendText = async (padID, text, authorId = '') => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -222,7 +212,7 @@ exports.appendText = async (padID, text, authorId = '') => {
const pad = await getPadSafe(padID, true);
await pad.appendText(text, authorId);
- await padMessageHandler.updatePadClients(pad);
+ await updatePadClients(pad);
};
/**
@@ -233,7 +223,7 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getHTML = async (padID, rev) => {
+export const getHTML = async (padID, rev) => {
if (rev !== undefined) {
rev = checkValidRev(rev);
}
@@ -250,7 +240,7 @@ exports.getHTML = async (padID, rev) => {
}
// get the html of this revision
- let html = await exportHtml.getPadHTML(pad, rev);
+ let html = await getPadHTMLDocument(pad, rev);
// wrap the HTML
html = `
${html}`;
@@ -265,7 +255,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.setHTML = async (padID, html, authorId = '') => {
+export const setHTML = async (padID, html, authorId = '') => {
// html string is required
if (typeof html !== 'string') {
throw new CustomError('html is not a string', 'apierror');
@@ -276,13 +266,13 @@ exports.setHTML = async (padID, html, authorId = '') => {
// add a new changeset with the new html to the pad
try {
- await importHtml.setPadHTML(pad, cleanText(html), authorId);
+ await setPadHTML(pad, cleanText(html), authorId);
} catch (e) {
throw new CustomError('HTML is malformed', 'apierror');
}
// update the clients on the pad
- padMessageHandler.updatePadClients(pad);
+ updatePadClients(pad);
};
/* ****************
@@ -303,7 +293,7 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getChatHistory = async (padID, start, end) => {
+export const getChatHistory = async (padID, start, end) => {
if (start && end) {
if (start < 0) {
throw new CustomError('start is below zero', 'apierror');
@@ -349,7 +339,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.appendChatMessage = async (padID, text, authorID, time) => {
+export const appendChatMessage = async (padID, text, authorID, time) => {
// text is required
if (typeof text !== 'string') {
throw new CustomError('text is not a string', 'apierror');
@@ -363,7 +353,7 @@ exports.appendChatMessage = async (padID, text, authorID, time) => {
// @TODO - missing getPadSafe() call ?
// save chat message to database and send message to all connected clients
- await padMessageHandler.sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
+ await sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
};
/* ***************
@@ -378,7 +368,7 @@ Example returns:
{code: 0, message:"ok", data: {revisions: 56}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getRevisionsCount = async (padID) => {
+export const getRevisionsCount = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {revisions: pad.getHeadRevisionNumber()};
@@ -392,7 +382,7 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getSavedRevisionsCount = async (padID) => {
+export const getSavedRevisionsCount = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsNumber()};
@@ -406,7 +396,7 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.listSavedRevisions = async (padID) => {
+export const listSavedRevisions = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsList()};
@@ -420,7 +410,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.saveRevision = async (padID, rev) => {
+export const saveRevision = async (padID, rev) => {
// check if rev is a number
if (rev !== undefined) {
rev = checkValidRev(rev);
@@ -439,7 +429,7 @@ exports.saveRevision = async (padID, rev) => {
rev = pad.getHeadRevisionNumber();
}
- const author = await authorManager.createAuthor('API');
+ const author = await createAuthor('API');
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
};
@@ -451,7 +441,7 @@ Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getLastEdited = async (padID) => {
+export const getLastEdited = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
const lastEdited = await pad.getLastEdit();
@@ -466,7 +456,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null}
*/
-exports.createPad = async (padID, text, authorId = '') => {
+export const createPad = async (padID, text, authorId = '') => {
if (padID) {
// ensure there is no $ in the padID
if (padID.indexOf('$') !== -1) {
@@ -491,7 +481,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.deletePad = async (padID) => {
+export const deletePad = async (padID) => {
const pad = await getPadSafe(padID, true);
await pad.remove();
};
@@ -504,7 +494,7 @@ exports.deletePad = async (padID) => {
{code:0, message:"ok", data:null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.restoreRevision = async (padID, rev, authorId = '') => {
+export const restoreRevision = async (padID, rev, authorId = '') => {
// check if rev is a number
if (rev === undefined) {
throw new CustomError('rev is not defined', 'apierror');
@@ -528,7 +518,7 @@ exports.restoreRevision = async (padID, rev, authorId = '') => {
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
- for (const op of Changeset.deserializeOps(attribs)) {
+ for (const op of deserializeOps(attribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
@@ -538,25 +528,25 @@ exports.restoreRevision = async (padID, rev, authorId = '') => {
};
// create a new changeset with a helper builder object
- const builder = Changeset.builder(oldText.length);
+ const builder2 = builder(oldText.length);
// assemble each line into the builder
eachAttribRun(atext.attribs, (start, end, attribs) => {
- builder.insert(atext.text.substring(start, end), attribs);
+ builder2.insert(atext.text.substring(start, end), attribs);
});
const lastNewlinePos = oldText.lastIndexOf('\n');
if (lastNewlinePos < 0) {
- builder.remove(oldText.length - 1, 0);
+ builder2.remove(oldText.length - 1, 0);
} else {
- builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
- builder.remove(oldText.length - lastNewlinePos - 1, 0);
+ builder2.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
+ builder2.remove(oldText.length - lastNewlinePos - 1, 0);
}
const changeset = builder.toString();
await pad.appendRevision(changeset, authorId);
- await padMessageHandler.updatePadClients(pad);
+ await updatePadClients(pad);
};
/**
@@ -568,7 +558,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.copyPad = async (sourceID, destinationID, force) => {
+export const copyPad = async (sourceID, destinationID, force) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
};
@@ -582,7 +572,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
+export const copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force, authorId);
};
@@ -596,7 +586,7 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.movePad = async (sourceID, destinationID, force) => {
+export const movePad = async (sourceID, destinationID, force) => {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
await pad.remove();
@@ -610,12 +600,12 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getReadOnlyID = async (padID) => {
+export const getReadOnlyID = async (padID) => {
// we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true);
// get the readonlyId
- const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
+ const readOnlyID = await getReadOnlyId(padID);
return {readOnlyID};
};
@@ -628,9 +618,9 @@ Example returns:
{code: 0, message:"ok", data: {padID: padID}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getPadID = async (roID) => {
+export const getPadID = async (roID) => {
// get the PadId
- const padID = await readOnlyManager.getPadId(roID);
+ const padID = await getPadId(roID);
if (padID == null) {
throw new CustomError('padID does not exist', 'apierror');
}
@@ -646,7 +636,7 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.setPublicStatus = async (padID, publicStatus) => {
+export const setPublicStatus = async (padID, publicStatus) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@@ -669,7 +659,7 @@ Example returns:
{code: 0, message:"ok", data: {publicStatus: true}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getPublicStatus = async (padID) => {
+export const getPublicStatus = async (padID) => {
// ensure this is a group pad
checkGroupPad(padID, 'publicStatus');
@@ -686,7 +676,7 @@ Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.listAuthorsOfPad = async (padID) => {
+export const listAuthorsOfPad = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
const authorIDs = pad.getAllAuthors();
@@ -716,9 +706,9 @@ Example returns:
{code: 1, message:"padID does not exist"}
*/
-exports.sendClientsMessage = async (padID, msg) => {
+export const sendClientsMessage = async (padID, msg) => {
await getPadSafe(padID, true); // Throw if the padID is invalid or if the pad does not exist.
- padMessageHandler.handleCustomMessage(padID, msg);
+ handleCustomMessage(padID, msg);
};
/**
@@ -729,7 +719,7 @@ Example returns:
{"code":0,"message":"ok","data":null}
{"code":4,"message":"no or wrong API Key","data":null}
*/
-exports.checkToken = async () => {
+export const checkToken = async () => {
};
/**
@@ -740,7 +730,7 @@ Example returns:
{code: 0, message:"ok", data: {chatHead: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
-exports.getChatHead = async (padID) => {
+export const getChatHead = async (padID) => {
// get the pad
const pad = await getPadSafe(padID, true);
return {chatHead: pad.chatHead};
@@ -764,7 +754,7 @@ Example returns:
{"code":4,"message":"no or wrong API Key","data":null}
*/
-exports.createDiffHTML = async (padID, startRev, endRev) => {
+export const createDiffHTML = async (padID, startRev, endRev) => {
// check if startRev is a number
if (startRev !== undefined) {
startRev = checkValidRev(startRev);
@@ -803,13 +793,13 @@ exports.createDiffHTML = async (padID, startRev, endRev) => {
{"code":4,"message":"no or wrong API Key","data":null}
*/
-exports.getStats = async () => {
- const sessionInfos = padMessageHandler.sessioninfos;
+export const getStats = async () => {
+ const sessionInfos = sessioninfos;
const sessionKeys = Object.keys(sessionInfos);
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
- const {padIDs} = await padManager.listAllPads();
+ const {padIDs} = await listAllPads();
return {
totalPads: padIDs.length,
@@ -826,19 +816,19 @@ exports.getStats = async () => {
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
// gets a pad safe
-const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
+const getPadSafe = async (padID, shouldExist, text?, authorId = '') => {
// check if padID is a string
if (typeof padID !== 'string') {
throw new CustomError('padID is not a string', 'apierror');
}
// check if the padID maches the requirements
- if (!padManager.isValidPadId(padID)) {
+ if (!isValidPadId(padID)) {
throw new CustomError('padID did not match requirements', 'apierror');
}
// check if the pad exists
- const exists = await padManager.doesPadExists(padID);
+ const exists = await doesPadExist(padID);
if (!exists && shouldExist) {
// does not exist, but should
@@ -851,7 +841,7 @@ const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
}
// pad exists, let's get it
- return padManager.getPad(padID, text, authorId);
+ return getPad(padID, text, authorId);
};
// checks if a rev is a legal number
diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.ts
similarity index 77%
rename from src/node/db/AuthorManager.js
rename to src/node/db/AuthorManager.ts
index 7049be5dba3..3dd316f675d 100644
--- a/src/node/db/AuthorManager.js
+++ b/src/node/db/AuthorManager.ts
@@ -19,12 +19,13 @@
* limitations under the License.
*/
-const db = require('./DB');
-const CustomError = require('../utils/customError');
-const hooks = require('../../static/js/pluginfw/hooks.js');
+import {db} from './DB';
+import {CustomError} from '../utils/customError';
+import {aCallFirst} from '../../static/js/pluginfw/hooks.js';
+
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
-exports.getColorPalette = () => [
+export const getColorPalette = () => [
'#ffc7c7',
'#fff1c7',
'#e3ffc7',
@@ -94,26 +95,23 @@ exports.getColorPalette = () => [
/**
* Checks if the author exists
*/
-exports.doesAuthorExist = async (authorID) => {
+export const doesAuthorExist = async (authorID: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
return author != null;
-};
-
-/* exported for backwards compatibility */
-exports.doesAuthorExists = exports.doesAuthorExist;
+}
-const getAuthor4Token = async (token) => {
+const getAuthor4Token2 = async (token: string) => {
const author = await mapAuthorWithDBKey('token2author', token);
// return only the sub value authorID
return author ? author.authorID : author;
};
-exports.getAuthorId = async (token, user) => {
+export const getAuthorId = async (token, user) => {
const context = {dbKey: token, token, user};
- let [authorId] = await hooks.aCallFirst('getAuthorId', context);
- if (!authorId) authorId = await getAuthor4Token(context.dbKey);
+ let [authorId] = await aCallFirst('getAuthorId', context);
+ if (!authorId) authorId = await getAuthor4Token2(context.dbKey);
return authorId;
};
@@ -123,23 +121,23 @@ exports.getAuthorId = async (token, user) => {
* @deprecated Use `getAuthorId` instead.
* @param {String} token The token
*/
-exports.getAuthor4Token = async (token) => {
+export const getAuthor4Token = async (token) => {
warnDeprecated(
'AuthorManager.getAuthor4Token() is deprecated; use AuthorManager.getAuthorId() instead');
- return await getAuthor4Token(token);
+ return await getAuthor4Token2(token);
};
/**
* Returns the AuthorID for a mapper.
- * @param {String} token The mapper
+ * @param authorMapper
* @param {String} name The name of the author (optional)
*/
-exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
+export const createAuthorIfNotExistsFor = async (authorMapper, 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;
@@ -151,13 +149,13 @@ exports.createAuthorIfNotExistsFor = async (authorMapper, name) => {
* @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper
*/
-const mapAuthorWithDBKey = async (mapperkey, mapper) => {
+export const mapAuthorWithDBKey = async (mapperkey: string, mapper) => {
// try to map to an author
const author = await db.get(`${mapperkey}:${mapper}`);
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);
@@ -178,13 +176,13 @@ const mapAuthorWithDBKey = async (mapperkey, mapper) => {
* Internal function that creates the database entry for an author
* @param {String} name The name of the author
*/
-exports.createAuthor = async (name) => {
+export const createAuthor = async (name) => {
// 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(),
};
@@ -199,41 +197,41 @@ exports.createAuthor = async (name) => {
* Returns the Author Obj of the author
* @param {String} author The id of the author
*/
-exports.getAuthor = async (author) => await db.get(`globalAuthor:${author}`);
+export 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) => await db.getSub(`globalAuthor:${author}`, ['colorId']);
+export 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, colorId) => await db.setSub(
+export 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) => await db.getSub(`globalAuthor:${author}`, ['name']);
+export 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, name) => await db.setSub(
+export const setAuthorName = async (author: string, name:string) => await db.setSub(
`globalAuthor:${author}`, ['name'], name);
/**
* Returns an array of all pads this author contributed to
- * @param {String} author The id of the author
+ * @param {String} authorID The id of the author
*/
-exports.listPadsOfAuthor = async (authorID) => {
+export 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
@@ -255,10 +253,10 @@ exports.listPadsOfAuthor = async (authorID) => {
/**
* Adds a new pad to the list of contributions
- * @param {String} author The id of the author
+ * @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
-exports.addPad = async (authorID, padID) => {
+export const addPad = async (authorID: string, padID: string) => {
// get the entry
const author = await db.get(`globalAuthor:${authorID}`);
@@ -282,10 +280,10 @@ exports.addPad = async (authorID, padID) => {
/**
* Removes a pad from the list of contributions
- * @param {String} author The id of the author
+ * @param {String} authorID The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
-exports.removePad = async (authorID, padID) => {
+export const removePad = async (authorID: string, padID?: string) => {
const author = await db.get(`globalAuthor:${authorID}`);
if (author == null) return;
diff --git a/src/node/db/DB.js b/src/node/db/DB.ts
similarity index 62%
rename from src/node/db/DB.js
rename to src/node/db/DB.ts
index 02e83f85de7..563ba5ec57d 100644
--- a/src/node/db/DB.js
+++ b/src/node/db/DB.ts
@@ -21,40 +21,43 @@
* limitations under the License.
*/
-const ueberDB = require('ueberdb2');
-const settings = require('../utils/Settings');
-const log4js = require('log4js');
-const stats = require('../stats');
-
+import {Database} from 'ueberdb2';
+import {dbSettings, dbType} from '../utils/Settings';
+import log4js from 'log4js';
+import {shutdown as statsShutdown,createCollection} from '../stats';
+import {} from 'measured-core'
const logger = log4js.getLogger('ueberDB');
/**
* The UeberDB Object that provides the database functions
*/
-exports.db = null;
+let db = null;
/**
* Initializes the database with the settings provided by the settings module
*/
-exports.init = async () => {
- exports.db = new ueberDB.Database(settings.dbType, settings.dbSettings, null, logger);
- await exports.db.init();
- if (exports.db.metrics != null) {
- for (const [metric, value] of Object.entries(exports.db.metrics)) {
+const init = async () => {
+ db = new Database(dbType, 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]);
+ // FIXME find a better replacement for measure-core
+ createCollection.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) => await f.call(exports.db, ...args);
+ const f = db[fn];
+ exports[fn] = async (...args) => await f.call(db, ...args);
Object.setPrototypeOf(exports[fn], Object.getPrototypeOf(f));
Object.defineProperties(exports[fn], Object.getOwnPropertyDescriptors(f));
}
};
-exports.shutdown = async (hookName, context) => {
- if (exports.db != null) await exports.db.close();
- exports.db = null;
+const shutdown = async (hookName, context) => {
+ if (db != null) await db.close();
+ db = null;
logger.log('Database closed');
};
+
+export {db,init,shutdown}
diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.ts
similarity index 81%
rename from src/node/db/GroupManager.js
rename to src/node/db/GroupManager.ts
index 4302048c409..5df48b2e31b 100644
--- a/src/node/db/GroupManager.js
+++ b/src/node/db/GroupManager.ts
@@ -19,13 +19,13 @@
* limitations under the License.
*/
-const CustomError = require('../utils/customError');
+import {CustomError} from '../utils/customError';
const randomString = require('../../static/js/pad_utils').randomString;
-const db = require('./DB');
-const padManager = require('./PadManager');
-const sessionManager = require('./SessionManager');
+import {db} from './DB';
+import {doesPadExist, getPad} from './PadManager';
+import {deleteSession} from './SessionManager';
-exports.listAllGroups = async () => {
+export const listAllGroups = async () => {
let groups = await db.get('groups');
groups = groups || {};
@@ -33,7 +33,7 @@ exports.listAllGroups = async () => {
return {groupIDs};
};
-exports.deleteGroup = async (groupID) => {
+export const deleteGroup = async (groupID) => {
const group = await db.get(`group:${groupID}`);
// ensure group exists
@@ -44,7 +44,7 @@ exports.deleteGroup = async (groupID) => {
// iterate through all pads of this group and delete them (in parallel)
await Promise.all(Object.keys(group.pads).map(async (padId) => {
- const pad = await padManager.getPad(padId);
+ const pad = await getPad(padId);
await pad.remove();
}));
@@ -52,7 +52,7 @@ exports.deleteGroup = async (groupID) => {
// record because deleting a session updates the group2sessions record.
const {sessionIDs = {}} = await db.get(`group2sessions:${groupID}`) || {};
await Promise.all(Object.keys(sessionIDs).map(async (sessionId) => {
- await sessionManager.deleteSession(sessionId);
+ await deleteSession(sessionId);
}));
await Promise.all([
@@ -68,14 +68,14 @@ exports.deleteGroup = async (groupID) => {
await db.remove(`group:${groupID}`);
};
-exports.doesGroupExist = async (groupID) => {
+export const doesGroupExist = async (groupID) => {
// try to get the group entry
const group = await db.get(`group:${groupID}`);
return (group != null);
};
-exports.createGroup = async () => {
+export 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
@@ -85,13 +85,13 @@ exports.createGroup = async () => {
return {groupID};
};
-exports.createGroupIfNotExistsFor = async (groupMapper) => {
+export const createGroupIfNotExistsFor = async (groupMapper) => {
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
@@ -103,19 +103,19 @@ exports.createGroupIfNotExistsFor = async (groupMapper) => {
return result;
};
-exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
+export const createGroupPad = async (groupID, padName, text, authorId = '') => {
// 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');
}
// ensure pad doesn't exist already
- const padExists = await padManager.doesPadExists(padID);
+ const padExists = await doesPadExist(padID);
if (padExists) {
// pad exists already
@@ -123,7 +123,7 @@ exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
}
// create the pad
- await padManager.getPad(padID, text, authorId);
+ await getPad(padID, text, authorId);
// create an entry in the group for this pad
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
@@ -131,8 +131,8 @@ exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
return {padID};
};
-exports.listPads = async (groupID) => {
- const exists = await exports.doesGroupExist(groupID);
+export const listPads = async (groupID) => {
+ const exists = await doesGroupExist(groupID);
// ensure the group exists
if (!exists) {
diff --git a/src/node/db/Pad.js b/src/node/db/Pad.ts
similarity index 82%
rename from src/node/db/Pad.js
rename to src/node/db/Pad.ts
index b692962f1c9..db5c5a4ca3e 100644
--- a/src/node/db/Pad.js
+++ b/src/node/db/Pad.ts
@@ -3,46 +3,63 @@
* The pad object, defined with joose
*/
-const AttributeMap = require('../../static/js/AttributeMap');
-const Changeset = require('../../static/js/Changeset');
-const ChatMessage = require('../../static/js/ChatMessage');
-const AttributePool = require('../../static/js/AttributePool');
-const Stream = require('../utils/Stream');
-const assert = require('assert').strict;
-const db = require('./DB');
-const settings = require('../utils/Settings');
-const authorManager = require('./AuthorManager');
-const padManager = require('./PadManager');
-const padMessageHandler = require('../handler/PadMessageHandler');
-const groupManager = require('./GroupManager');
-const CustomError = require('../utils/customError');
-const readOnlyManager = require('./ReadOnlyManager');
-const randomString = require('../utils/randomstring');
-const hooks = require('../../static/js/pluginfw/hooks');
-const {padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
-const promises = require('../utils/promises');
-
+import AttributeMap from '../../static/js/AttributeMap';
+import {
+ applyToAText, checkRep,
+ copyAText, deserializeOps,
+ makeAText,
+ makeSplice,
+ opsFromAText,
+ pack,
+ smartOpAssembler, unpack
+} from '../../static/js/Changeset';
+import ChatMessage from '../../static/js/ChatMessage';
+import {AttributePool} from '../../static/js/AttributePool';
+import {Stream} from '../utils/Stream';
+import assert, {strict} from 'assert'
+import {db} from './DB';
+import {defaultPadText} from '../utils/Settings';
+import {addPad, getAuthorColorId, getAuthorName, getColorPalette, removePad} from './AuthorManager';
+import {Revision} from "../models/Revision";
+import {doesPadExist, getPad} from './PadManager';
+import {kickSessionsFromPad} from '../handler/PadMessageHandler';
+import {doesGroupExist} from './GroupManager';
+import {CustomError} from '../utils/customError';
+import {getReadOnlyId} from './ReadOnlyManager';
+import {randomString} from '../utils/randomstring';
+import {aCallAll} from '../../static/js/pluginfw/hooks';
+import {timesLimit} from '../utils/promises';
+import {padutils} from '../../static/js/pad_utils';
/**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
* line breaks and convert Tabs to spaces
* @param txt
*/
-exports.cleanText = (txt) => txt.replace(/\r\n/g, '\n')
+export const cleanText = (txt) => txt.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\xa0/g, ' ');
-class Pad {
+export class Pad {
+ private db: any;
+ private atext: any;
+ private pool: AttributePool;
+ private head: number;
+ private chatHead: number;
+ private publicStatus: boolean;
+ private id: string;
+ private savedRevisions: Revision[];
/**
+ * @param id the id of this pad
* @param [database] - Database object to access this pad's records (and only this pad's records;
* the shared global Etherpad database object is still used for all other pad accesses, such
* as copying the pad). Defaults to the shared global Etherpad database object. This parameter
* can be used to shard pad storage across multiple database backends, to put each pad in its
* own database table, or to validate imported pad data before it is written to the database.
*/
- constructor(id, database = db) {
+ constructor(id: string, database = db) {
this.db = database;
- this.atext = Changeset.makeAText('\n');
+ this.atext = makeAText('\n');
this.pool = new AttributePool();
this.head = -1;
this.chatHead = -1;
@@ -74,11 +91,11 @@ class Pad {
}
async appendRevision(aChangeset, authorId = '') {
- const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
+ const newAText = applyToAText(aChangeset, this.atext, this.pool);
if (newAText.text === this.atext.text && newAText.attribs === this.atext.attribs) {
return this.head;
}
- Changeset.copyAText(newAText, this.atext);
+ copyAText(newAText, this.atext);
const newRev = ++this.head;
@@ -99,16 +116,16 @@ class Pad {
},
}),
this.saveToDatabase(),
- authorId && authorManager.addPad(authorId, this.id),
- hooks.aCallAll(hook, {
+ authorId && addPad(authorId, this.id),
+ aCallAll(hook, {
pad: this,
authorId,
get author() {
- warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
+ padutils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
return this.authorId;
},
set author(authorId) {
- warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
+ padutils.warnDeprecated(`${hook} hook author context is deprecated; use authorId instead`);
this.authorId = authorId;
},
...this.head === 0 ? {} : {
@@ -121,7 +138,7 @@ class Pad {
}
toJSON() {
- const o = {...this, pool: this.pool.toJsonable()};
+ const o:{db: any, id: any} = {...this, pool: this.pool.toJsonable()}
delete o.db;
delete o.id;
return o;
@@ -179,7 +196,7 @@ class Pad {
]);
const apool = this.apool();
let atext = keyAText;
- for (const cs of changesets) atext = Changeset.applyToAText(cs, atext, apool);
+ for (const cs of changesets) atext = applyToAText(cs, atext, apool);
return atext;
}
@@ -190,10 +207,10 @@ class Pad {
async getAllAuthorColors() {
const authorIds = this.getAllAuthors();
const returnTable = {};
- const colorPalette = authorManager.getColorPalette();
+ const colorPalette = getColorPalette();
await Promise.all(
- authorIds.map((authorId) => authorManager.getAuthorColorId(authorId).then((colorId) => {
+ authorIds.map((authorId) => getAuthorColorId(authorId).then((colorId) => {
// colorId might be a hex color or an number out of the palette
returnTable[authorId] = colorPalette[colorId] || colorId;
})));
@@ -250,14 +267,14 @@ 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') ||
(!ins && start > 0 && orig[start - 1] === '\n');
if (!willEndWithNewline) ins += '\n';
if (ndel === 0 && ins.length === 0) return;
- const changeset = Changeset.makeSplice(orig, start, ndel, ins);
+ const changeset = makeSplice(orig, start, ndel, ins);
await this.appendRevision(changeset, authorId);
}
@@ -315,7 +332,7 @@ class Pad {
const entry = await this.db.get(`pad:${this.id}:chat:${entryNum}`);
if (entry == null) return null;
const message = ChatMessage.fromObject(entry);
- message.displayName = await authorManager.getAuthorName(message.authorId);
+ message.displayName = await getAuthorName(message.authorId);
return message;
}
@@ -352,15 +369,15 @@ class Pad {
if ('pool' in value) this.pool = new AttributePool().fromJsonable(value.pool);
} else {
if (text == null) {
- const context = {pad: this, authorId, type: 'text', content: settings.defaultPadText};
- await hooks.aCallAll('padDefaultContent', context);
+ const context = {pad: this, authorId, type: 'text', content: defaultPadText};
+ await 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 = Changeset.makeSplice('\n', 0, 0, text);
+ const firstChangeset = makeSplice('\n', 0, 0, text);
await this.appendRevision(firstChangeset, authorId);
}
- await hooks.aCallAll('padLoad', {pad: this});
+ await aCallAll('padLoad', {pad: this});
}
async copy(destinationID, force) {
@@ -393,16 +410,16 @@ class Pad {
for (const p of new Stream(promises).batch(100).buffer(99)) await p;
// Initialize the new pad (will update the listAllPads cache)
- const dstPad = await padManager.getPad(destinationID, null);
+ const dstPad = await getPad(destinationID, null);
// let the plugins know the pad was copied
- await hooks.aCallAll('padCopy', {
+ await aCallAll('padCopy', {
get originalPad() {
- warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
+ padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
return this.srcPad;
},
get destinationID() {
- warnDeprecated(
+ padutils.warnDeprecated(
'padCopy destinationID context property is deprecated; use dstPad.id instead');
return this.dstPad.id;
},
@@ -418,7 +435,7 @@ class Pad {
if (destinationID.indexOf('$') >= 0) {
destGroupID = destinationID.split('$')[0];
- const groupExists = await groupManager.doesGroupExist(destGroupID);
+ const groupExists = await doesGroupExist(destGroupID);
// group does not exist
if (!groupExists) {
@@ -430,7 +447,7 @@ class Pad {
async removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
// if the pad exists, we should abort, unless forced.
- const exists = await padManager.doesPadExist(destinationID);
+ const exists = await doesPadExist(destinationID);
// allow force to be a string
if (typeof force === 'string') {
@@ -446,7 +463,7 @@ class Pad {
}
// exists and forcing
- const pad = await padManager.getPad(destinationID);
+ const pad = await getPad(destinationID);
await pad.remove();
}
}
@@ -454,7 +471,7 @@ class Pad {
async copyAuthorInfoToDestinationPad(destinationID) {
// add the new sourcePad to all authors who contributed to the old one
await Promise.all(this.getAllAuthors().map(
- (authorID) => authorManager.addPad(authorID, destinationID)));
+ (authorID) => addPad(authorID, destinationID)));
}
async copyPadWithoutHistory(destinationID, force, authorId = '') {
@@ -475,14 +492,14 @@ class Pad {
}
// initialize the pad with a new line to avoid getting the defaultText
- const dstPad = await padManager.getPad(destinationID, '\n', authorId);
+ const dstPad = await getPad(destinationID, '\n', authorId);
dstPad.pool = this.pool.clone();
const oldAText = this.atext;
// based on Changeset.makeSplice
- const assem = Changeset.smartOpAssembler();
- for (const op of Changeset.opsFromAText(oldAText)) assem.append(op);
+ const assem = smartOpAssembler();
+ for (const op of opsFromAText(oldAText)) assem.append(op);
assem.endDocument();
// although we have instantiated the dstPad with '\n', an additional '\n' is
@@ -494,16 +511,16 @@ class Pad {
// create a changeset that removes the previous text and add the newText with
// all atributes present on the source pad
- const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
+ const changeset = pack(oldLength, newLength, assem.toString(), newText);
dstPad.appendRevision(changeset, authorId);
- await hooks.aCallAll('padCopy', {
+ await aCallAll('padCopy', {
get originalPad() {
- warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
+ padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
return this.srcPad;
},
get destinationID() {
- warnDeprecated(
+ padutils.warnDeprecated(
'padCopy destinationID context property is deprecated; use dstPad.id instead');
return this.dstPad.id;
},
@@ -519,7 +536,7 @@ class Pad {
const p = [];
// kick everyone from this pad
- padMessageHandler.kickSessionsFromPad(padID);
+ kickSessionsFromPad(padID);
// delete all relations - the original code used async.parallel but
// none of the operations except getting the group depended on callbacks
@@ -540,31 +557,31 @@ class Pad {
}
// remove the readonly entries
- p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
+ p.push(getReadOnlyId(padID).then(async (readonlyID) => {
await db.remove(`readonly2pad:${readonlyID}`);
}));
p.push(db.remove(`pad2readonly:${padID}`));
// delete all chat messages
- p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
+ p.push(timesLimit(this.chatHead + 1, 500, async (i) => {
await this.db.remove(`pad:${this.id}:chat:${i}`, null);
}));
// delete all revisions
- p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
+ p.push(timesLimit(this.head + 1, 500, async (i) => {
await this.db.remove(`pad:${this.id}:revs:${i}`, null);
}));
// remove pad from all authors who contributed
this.getAllAuthors().forEach((authorId) => {
- p.push(authorManager.removePad(authorId, padID));
+ p.push(removePad(authorId, padID));
});
// delete the pad entry and delete pad from padManager
- p.push(padManager.removePad(padID));
- p.push(hooks.aCallAll('padRemove', {
+ p.push(removePad(padID));
+ p.push(aCallAll('padRemove', {
get padID() {
- warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
+ padutils.warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
return this.pad.id;
},
pad: this,
@@ -587,12 +604,13 @@ class Pad {
}
// build the saved revision object
- const savedRevision = {};
- savedRevision.revNum = revNum;
- savedRevision.savedById = savedById;
- savedRevision.label = label || `Revision ${revNum}`;
- savedRevision.timestamp = Date.now();
- savedRevision.id = randomString(10);
+ const savedRevision:Revision = {
+ label: label || `Revision ${revNum}`,
+ revNum: revNum,
+ savedById: savedById,
+ timestamp: Date.now(),
+ id: randomString(10)
+ }
// save this new saved revision
this.savedRevisions.push(savedRevision);
@@ -667,7 +685,7 @@ class Pad {
}
})
.batch(100).buffer(99);
- let atext = Changeset.makeAText('\n');
+ let atext = makeAText('\n');
for await (const [r, changeset, authorId, timestamp, isKeyRev, keyAText] of revs) {
try {
assert(authorId != null);
@@ -678,10 +696,10 @@ class Pad {
assert(timestamp > 0);
assert(changeset != null);
assert.equal(typeof changeset, 'string');
- Changeset.checkRep(changeset);
- const unpacked = Changeset.unpack(changeset);
+ checkRep(changeset);
+ const unpacked = unpack(changeset);
let text = atext.text;
- for (const op of Changeset.deserializeOps(unpacked.ops)) {
+ for (const op of deserializeOps(unpacked.ops)) {
if (['=', '-'].includes(op.opcode)) {
assert(text.length >= op.chars);
const consumed = text.slice(0, op.chars);
@@ -692,7 +710,7 @@ class Pad {
}
assert.equal(op.attribs, AttributeMap.fromString(op.attribs, pool).toString());
}
- atext = Changeset.applyToAText(changeset, atext, pool);
+ atext = applyToAText(changeset, atext, pool);
if (isKeyRev) assert.deepEqual(keyAText, atext);
} catch (err) {
err.message = `(pad ${this.id} revision ${r}) ${err.message}`;
@@ -720,7 +738,6 @@ class Pad {
.batch(100).buffer(99);
for (const p of chats) await p;
- await hooks.aCallAll('padCheck', {pad: this});
+ await aCallAll('padCheck', {pad: this});
}
}
-exports.Pad = Pad;
diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.ts
similarity index 86%
rename from src/node/db/PadManager.js
rename to src/node/db/PadManager.ts
index e419e839231..a48473546d8 100644
--- a/src/node/db/PadManager.js
+++ b/src/node/db/PadManager.ts
@@ -19,9 +19,9 @@
* limitations under the License.
*/
-const CustomError = require('../utils/customError');
-const Pad = require('../db/Pad');
-const db = require('./DB');
+import {CustomError} from '../utils/customError';
+import {Pad} from './Pad';
+import {db} from './DB';
/**
* A cache of all loaded Pads.
@@ -50,6 +50,9 @@ const globalPads = {
* Updated without db access as new pads are created/old ones removed.
*/
const padList = new class {
+ private _cachedList: any[];
+ private _list: Set;
+ private _loaded: Promise
constructor() {
this._cachedList = null;
this._list = new Set();
@@ -94,9 +97,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, text, authorId = '') => {
+export const getPad = async (id, text?, authorId = '') => {
// check if this is a valid padId
- if (!exports.isValidPadId(id)) {
+ if (!isValidPadId(id)) {
throw new CustomError(`${id} is not a valid padId`, 'apierror');
}
@@ -121,7 +124,7 @@ exports.getPad = async (id, text, authorId = '') => {
}
// try to load pad
- pad = new Pad.Pad(id);
+ pad = new Pad(id);
// initialize the pad
await pad.init(text, authorId);
@@ -131,21 +134,18 @@ exports.getPad = async (id, text, authorId = '') => {
return pad;
};
-exports.listAllPads = async () => {
+export const listAllPads = async () => {
const padIDs = await padList.getPads();
return {padIDs};
};
// checks if a pad exists
-exports.doesPadExist = async (padId) => {
+export const doesPadExist = async (padId) => {
const value = await db.get(`pad:${padId}`);
return (value != null && value.atext);
-};
-
-// alias for backwards compatibility
-exports.doesPadExists = exports.doesPadExist;
+}
/**
* An array of padId transformations. These represent changes in pad name policy over
@@ -157,9 +157,9 @@ const padIdTransforms = [
];
// returns a sanitized padId, respecting legacy pad id formats
-exports.sanitizePadId = async (padId) => {
+export const sanitizePadId = async (padId) => {
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;
@@ -174,19 +174,19 @@ exports.sanitizePadId = async (padId) => {
return padId;
};
-exports.isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
+export const isValidPadId = (padId) => /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
/**
* Removes the pad from database and unloads it.
*/
-exports.removePad = async (padId) => {
+export const removePad = async (padId) => {
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) => {
+export const unloadPad = (padId) => {
globalPads.remove(padId);
};
diff --git a/src/node/db/ReadOnlyManager.js b/src/node/db/ReadOnlyManager.ts
similarity index 72%
rename from src/node/db/ReadOnlyManager.js
rename to src/node/db/ReadOnlyManager.ts
index 33ce2930a4b..a96ad53ed60 100644
--- a/src/node/db/ReadOnlyManager.js
+++ b/src/node/db/ReadOnlyManager.ts
@@ -20,21 +20,21 @@
*/
-const db = require('./DB');
-const randomString = require('../utils/randomstring');
+import {db} from './DB';
+import {randomString} from '../utils/randomstring';
/**
* checks if the id pattern matches a read-only pad id
- * @param {String} the pad's id
+ * @param {String} id the pad's id
*/
-exports.isReadOnlyId = (id) => id.startsWith('r.');
+export const isReadOnlyId = (id: string) => id.startsWith('r.');
/**
* returns a read only id for a pad
* @param {String} padId the id of the pad
*/
-exports.getReadOnlyId = async (padId) => {
+export const getReadOnlyId = async (padId) => {
// check if there is a pad2readonly entry
let readOnlyId = await db.get(`pad2readonly:${padId}`);
@@ -54,18 +54,18 @@ exports.getReadOnlyId = async (padId) => {
* returns the padId for a read only id
* @param {String} readOnlyId read only id
*/
-exports.getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readOnlyId}`);
+export const getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readOnlyId}`);
/**
* returns the padId and readonlyPadId in an object for any id
- * @param {String} padIdOrReadonlyPadId read only id or real pad id
+ * @param {String} id padIdOrReadonlyPadId read only id or real pad id
*/
-exports.getIds = async (id) => {
- const readonly = exports.isReadOnlyId(id);
+export const getIds = async (id: string) => {
+ const readonly = isReadOnlyId(id);
// Might be null, if this is an unknown read-only id
- const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
- const padId = readonly ? await exports.getPadId(id) : id;
+ const readOnlyPadId = readonly ? id : await getReadOnlyId(id);
+ const padId = readonly ? await getPadId(id) : id;
return {readOnlyPadId, padId, readonly};
};
diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.ts
similarity index 76%
rename from src/node/db/SecurityManager.js
rename to src/node/db/SecurityManager.ts
index 280c753bba4..f0fc26551fe 100644
--- a/src/node/db/SecurityManager.js
+++ b/src/node/db/SecurityManager.ts
@@ -19,18 +19,29 @@
* limitations under the License.
*/
-const authorManager = require('./AuthorManager');
-const hooks = require('../../static/js/pluginfw/hooks.js');
-const padManager = require('./PadManager');
-const readOnlyManager = require('./ReadOnlyManager');
-const sessionManager = require('./SessionManager');
-const settings = require('../utils/Settings');
-const webaccess = require('../hooks/express/webaccess');
-const log4js = require('log4js');
+import {getAuthorId} from "./AuthorManager";
+
+import {callAll} from "../../static/js/pluginfw/hooks.js";
+
+import {doesPadExist, getPad} from "./PadManager";
+
+import {getPadId} from "./ReadOnlyManager";
+
+import {findAuthorID} from "./SessionManager";
+
+import {editOnly, loadTest, requireAuthentication, requireSession} from "../utils/Settings";
+
+import {normalizeAuthzLevel} from "../hooks/express/webaccess";
+
+import log4js from "log4js";
+
+import {padutils} from "../../static/js/pad_utils";
+import {isReadOnlyId} from "./ReadOnlyManager.js";
+
const authLogger = log4js.getLogger('auth');
-const {padutils} = require('../../static/js/pad_utils');
-const DENY = Object.freeze({accessStatus: 'deny'});
+
+const DENY = Object.freeze({accessStatus: 'deny', authorID: null});
/**
* Determines whether the user can access a pad.
@@ -50,17 +61,17 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
*/
-exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
+export const checkAccess = async (padID, sessionCookie, token, userSettings) => {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;
}
- let canCreate = !settings.editOnly;
+ let canCreate = !editOnly;
- if (readOnlyManager.isReadOnlyId(padID)) {
+ if (isReadOnlyId(padID)) {
canCreate = false;
- padID = await readOnlyManager.getPadId(padID);
+ padID = await getPadId(padID);
if (padID == null) {
authLogger.debug('access denied: read-only pad ID for a pad that does not exist');
return DENY;
@@ -68,10 +79,10 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
}
// Authentication and authorization checks.
- if (settings.loadTest) {
+ if (loadTest) {
console.warn(
'bypassing socket.io authentication and authorization checks due to settings.loadTest');
- } else if (settings.requireAuthentication) {
+ } else if (requireAuthentication) {
if (userSettings == null) {
authLogger.debug('access denied: authentication is required');
return DENY;
@@ -81,7 +92,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
// Note: userSettings.padAuthorizations should still be populated even if
// settings.requireAuthorization is false.
const padAuthzs = userSettings.padAuthorizations || {};
- const level = webaccess.normalizeAuthzLevel(padAuthzs[padID]);
+ const level = normalizeAuthzLevel(padAuthzs[padID]);
if (!level) {
authLogger.debug('access denied: unauthorized');
return DENY;
@@ -91,19 +102,19 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
// allow plugins to deny access
const isFalse = (x) => x === false;
- if (hooks.callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
+ if (callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
authLogger.debug('access denied: an onAccessCheck hook function returned false');
return DENY;
}
- const padExists = await padManager.doesPadExist(padID);
+ const padExists = await doesPadExist(padID);
if (!padExists && !canCreate) {
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
return DENY;
}
- const sessionAuthorID = await sessionManager.findAuthorID(padID.split('$')[0], sessionCookie);
- if (settings.requireSession && !sessionAuthorID) {
+ const sessionAuthorID = await findAuthorID(padID.split('$')[0], sessionCookie);
+ if (requireSession && !sessionAuthorID) {
authLogger.debug('access denied: HTTP API session is required');
return DENY;
}
@@ -115,7 +126,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
const grant = {
accessStatus: 'grant',
- authorID: sessionAuthorID || await authorManager.getAuthorId(token, userSettings),
+ authorID: sessionAuthorID || await getAuthorId(token, userSettings),
};
if (!padID.includes('$')) {
@@ -132,7 +143,7 @@ exports.checkAccess = async (padID, sessionCookie, token, userSettings) => {
return grant;
}
- const pad = await padManager.getPad(padID);
+ const pad = await getPad(padID);
if (!pad.getPublicStatus() && sessionAuthorID == null) {
authLogger.debug('access denied: must have an HTTP API session to access private group pads');
diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.ts
similarity index 93%
rename from src/node/db/SessionManager.js
rename to src/node/db/SessionManager.ts
index 5ef75acfaf4..341252e26a0 100644
--- a/src/node/db/SessionManager.js
+++ b/src/node/db/SessionManager.ts
@@ -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, sessionCookie) => {
+export const findAuthorID = async (groupID, sessionCookie) => {
if (!sessionCookie) return undefined;
/*
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
@@ -64,7 +64,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
const sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
const sessionInfoPromises = sessionIDs.map(async (id) => {
try {
- return await exports.getSessionInfo(id);
+ return await getSessionInfo(id);
} catch (err) {
if (err.message === 'sessionID does not exist') {
console.debug(`SessionManager getAuthorID: no session exists with ID ${id}`);
@@ -81,7 +81,7 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
return sessionInfo.authorID;
};
-exports.doesSessionExist = async (sessionID) => {
+export const doesSessionExist = async (sessionID) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
return (session != null);
@@ -90,7 +90,7 @@ exports.doesSessionExist = async (sessionID) => {
/**
* Creates a new session between an author and a group
*/
-exports.createSession = async (groupID, authorID, validUntil) => {
+export const createSession = async (groupID, authorID, validUntil) => {
// check if the group exists
const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) {
@@ -146,7 +146,7 @@ exports.createSession = async (groupID, authorID, validUntil) => {
return {sessionID};
};
-exports.getSessionInfo = async (sessionID) => {
+export const getSessionInfo = async (sessionID) => {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
@@ -162,7 +162,7 @@ exports.getSessionInfo = async (sessionID) => {
/**
* Deletes a session
*/
-exports.deleteSession = async (sessionID) => {
+export const deleteSession = async (sessionID) => {
// ensure that the session exists
const session = await db.get(`session:${sessionID}`);
if (session == null) {
@@ -186,7 +186,7 @@ exports.deleteSession = async (sessionID) => {
await db.remove(`session:${sessionID}`);
};
-exports.listSessionsOfGroup = async (groupID) => {
+export const listSessionsOfGroup = async (groupID) => {
// check that the group exists
const exists = await groupManager.doesGroupExist(groupID);
if (!exists) {
@@ -197,7 +197,7 @@ exports.listSessionsOfGroup = async (groupID) => {
return sessions;
};
-exports.listSessionsOfAuthor = async (authorID) => {
+export const listSessionsOfAuthor = async (authorID) => {
// check that the author exists
const exists = await authorManager.doesAuthorExist(authorID);
if (!exists) {
@@ -218,8 +218,7 @@ const listSessionsWithDBKey = async (dbkey) => {
// iterate through the sessions and get the sessioninfos
for (const sessionID of Object.keys(sessions || {})) {
try {
- const sessionInfo = await exports.getSessionInfo(sessionID);
- sessions[sessionID] = sessionInfo;
+ sessions[sessionID] = await getSessionInfo(sessionID);
} catch (err) {
if (err.name === 'apierror') {
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.ts
similarity index 88%
rename from src/node/db/SessionStore.js
rename to src/node/db/SessionStore.ts
index 40e5e90d069..bd6ab9d92e0 100644
--- a/src/node/db/SessionStore.js
+++ b/src/node/db/SessionStore.ts
@@ -1,13 +1,19 @@
'use strict';
-const DB = require('./DB');
-const Store = require('express-session').Store;
-const log4js = require('log4js');
-const util = require('util');
+import {db} from "./DB";
+
+import {Store} from "express-session";
+
+import log4js from "log4js";
+
+import util from "util";
+import {SessionModel} from "../models/SessionModel";
const logger = log4js.getLogger('SessionStore');
class SessionStore extends Store {
+ private _refresh: any;
+ private _expirations: Map;
/**
* @param {?number} [refresh] - How often (in milliseconds) `touch()` will update a session's
* database record with the cookie's latest expiration time. If the difference between the
@@ -34,10 +40,10 @@ class SessionStore extends Store {
for (const {timeout} of this._expirations.values()) clearTimeout(timeout);
}
- async _updateExpirations(sid, sess, updateDbExp = true) {
+ async _updateExpirations(sid, sess: SessionModel, updateDbExp = true) {
const exp = this._expirations.get(sid) || {};
clearTimeout(exp.timeout);
- const {cookie: {expires} = {}} = sess || {};
+ const {cookie: {expires} = {expires: sess.cookie.expires}} = sess || {cookie:{expires:undefined}};
if (expires) {
const sessExp = new Date(expires).getTime();
if (updateDbExp) exp.db = sessExp;
@@ -64,12 +70,12 @@ class SessionStore extends Store {
}
async _write(sid, sess) {
- await DB.set(`sessionstorage:${sid}`, sess);
+ await db.set(`sessionstorage:${sid}`, sess);
}
async _get(sid) {
logger.debug(`GET ${sid}`);
- const s = await DB.get(`sessionstorage:${sid}`);
+ const s = await db.get(`sessionstorage:${sid}`);
return await this._updateExpirations(sid, s);
}
@@ -83,7 +89,7 @@ class SessionStore extends Store {
logger.debug(`DESTROY ${sid}`);
clearTimeout((this._expirations.get(sid) || {}).timeout);
this._expirations.delete(sid);
- await DB.remove(`sessionstorage:${sid}`);
+ await db.remove(`sessionstorage:${sid}`);
}
// Note: express-session might call touch() before it calls set() for the first time. Ideally this
@@ -110,4 +116,4 @@ for (const m of ['get', 'set', 'destroy', 'touch']) {
SessionStore.prototype[m] = util.callbackify(SessionStore.prototype[`_${m}`]);
}
-module.exports = SessionStore;
+export default SessionStore;
diff --git a/src/node/eejs/index.js b/src/node/eejs/index.ts
similarity index 53%
rename from src/node/eejs/index.js
rename to src/node/eejs/index.ts
index 1bf29634b1b..ff125e4c373 100644
--- a/src/node/eejs/index.js
+++ b/src/node/eejs/index.ts
@@ -20,56 +20,61 @@
* require("./index").require("./path/to/template.ejs")
*/
-const ejs = require('ejs');
-const fs = require('fs');
-const hooks = require('../../static/js/pluginfw/hooks.js');
-const path = require('path');
-const resolve = require('resolve');
-const settings = require('../utils/Settings');
+import ejs from 'ejs';
+import fs from "fs";
+
+import {callAll} from "../../static/js/pluginfw/hooks.js";
+
+import path from "path";
+
+import resolve from "resolve";
+
+import {maxAge} from "../utils/Settings";
const templateCache = new Map();
-exports.info = {
+export const info = {
__output_stack: [],
block_stack: [],
file_stack: [],
- args: [],
+ args: [], __output: undefined
+
};
-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, recursive) => {
- exports.info.__output_stack.push(exports.info.__output);
- exports.info.__output = b;
+export const _init = (b, recursive) => {
+ info.__output_stack.push(info.__output)
+ info.__output = b
};
-exports._exit = (b, recursive) => {
- exports.info.__output = exports.info.__output_stack.pop();
+export const _exit = (b, recursive) => {
+ info.__output = info.__output_stack.pop();
};
-exports.begin_block = (name) => {
- exports.info.block_stack.push(name);
- exports.info.__output_stack.push(exports.info.__output.get());
- exports.info.__output.set('');
+export const begin_block = (name) => {
+ 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));
+ callAll(`eejsBlock_${name}`, args);
+ info.__output.set(info.__output.get().concat(args.content));
};
-exports.require = (name, args, mod) => {
+export const required = (name, args?, mod?) => {
if (args == null) args = {};
let basedir = __dirname;
let paths = [];
- if (exports.info.file_stack.length) {
+ if (info.file_stack.length) {
basedir = path.dirname(getCurrentFile().path);
}
if (mod) {
@@ -82,18 +87,18 @@ exports.require = (name, args, mod) => {
args.e = exports;
args.require = require;
- const cache = settings.maxAge !== 0;
+ const cache = maxAge !== 0;
const template = cache && templateCache.get(ejspath) || ejs.compile(
'<% e._init({get: () => __output, set: (s) => { __output = s; }}); %>' +
`${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`,
{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;
};
diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.ts
similarity index 88%
rename from src/node/handler/APIHandler.js
rename to src/node/handler/APIHandler.ts
index 2c060c23953..5b485f33a4a 100644
--- a/src/node/handler/APIHandler.js
+++ b/src/node/handler/APIHandler.ts
@@ -19,12 +19,12 @@
* limitations under the License.
*/
-const absolutePaths = require('../utils/AbsolutePaths');
-const fs = require('fs');
-const api = require('../db/API');
-const log4js = require('log4js');
-const padManager = require('../db/PadManager');
-const randomString = require('../utils/randomstring');
+import {makeAbsolute} from '../utils/AbsolutePaths';
+import fs from 'fs';
+import * as api from '../db/API';
+import log4js from 'log4js';
+import {sanitizePadId} from '../db/PadManager';
+import {randomString} from '../utils/randomstring';
const argv = require('../utils/Cli').argv;
const createHTTPError = require('http-errors');
@@ -32,7 +32,7 @@ const apiHandlerLogger = log4js.getLogger('APIHandler');
// ensure we have an apikey
let apikey = null;
-const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
+const apikeyFilename = makeAbsolute(argv.apikey || './APIKEY.txt');
try {
apikey = fs.readFileSync(apikeyFilename, 'utf8');
@@ -45,7 +45,7 @@ try {
}
// a list of all functions
-const version = {};
+export const version = {};
version['1'] = {
createGroup: [],
@@ -158,10 +158,9 @@ version['1.3.0'] = {
};
// set the latest available API version here
-exports.latestApiVersion = '1.3.0';
+export const latestApiVersion = '1.3.0';
// exports the versions so it can be used by the new Swagger endpoint
-exports.version = version;
/**
* Handles a HTTP API call
@@ -170,7 +169,7 @@ exports.version = version;
* @req express request object
* @res express response object
*/
-exports.handle = async function (apiVersion, functionName, fields, req, res) {
+export const handle = async function (apiVersion, functionName, fields, req, res) {
// say goodbye if this is an unknown API version
if (!(apiVersion in version)) {
throw new createHTTPError.NotFound('no such api version');
@@ -190,13 +189,13 @@ exports.handle = async function (apiVersion, functionName, fields, req, res) {
// sanitize any padIDs before continuing
if (fields.padID) {
- fields.padID = await padManager.sanitizePadId(fields.padID);
+ fields.padID = await sanitizePadId(fields.padID);
}
// there was an 'else' here before - removed it to ensure
// that this sanitize step can't be circumvented by forcing
// the first branch to be taken
if (fields.padName) {
- fields.padName = await padManager.sanitizePadId(fields.padName);
+ fields.padName = await sanitizePadId(fields.padName);
}
// put the function parameters in an array
@@ -206,6 +205,6 @@ exports.handle = async function (apiVersion, functionName, fields, req, res) {
return api[functionName].apply(this, functionParams);
};
-exports.exportedForTestingOnly = {
+export const exportedForTestingOnly = {
apiKey: apikey,
};
diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.ts
similarity index 98%
rename from src/node/handler/ExportHandler.js
rename to src/node/handler/ExportHandler.ts
index f3fde047cd5..a06c2839331 100644
--- a/src/node/handler/ExportHandler.js
+++ b/src/node/handler/ExportHandler.ts
@@ -38,7 +38,7 @@ const tempDirectory = os.tmpdir();
/**
* do a requested export
*/
-exports.doExport = async (req, res, padId, readOnlyId, type) => {
+export const doExport = async (req, res, padId, readOnlyId, type) => {
// avoid naming the read-only file as the original pad's id
let fileName = readOnlyId ? readOnlyId : padId;
@@ -114,4 +114,4 @@ exports.doExport = async (req, res, padId, readOnlyId, type) => {
await fsp_unlink(destFile);
}
-};
+}
diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.ts
similarity index 84%
rename from src/node/handler/ImportHandler.js
rename to src/node/handler/ImportHandler.ts
index c1fbc94d03a..d83c15c6721 100644
--- a/src/node/handler/ImportHandler.js
+++ b/src/node/handler/ImportHandler.ts
@@ -21,22 +21,24 @@
* limitations under the License.
*/
-const padManager = require('../db/PadManager');
-const padMessageHandler = require('./PadMessageHandler');
-const fs = require('fs').promises;
-const path = require('path');
-const settings = require('../utils/Settings');
-const {Formidable} = require('formidable');
-const os = require('os');
-const importHtml = require('../utils/ImportHtml');
-const importEtherpad = require('../utils/ImportEtherpad');
-const log4js = require('log4js');
-const hooks = require('../../static/js/pluginfw/hooks.js');
+import {getPad, unloadPad} from '../db/PadManager';
+import {updatePadClients} from './PadMessageHandler';
+import path from 'path';
+import {promises as fs} from "fs";
+
+import {abiword, allowUnknownFileEnds, importMaxFileSize, soffice} from '../utils/Settings';
+import {Formidable} from 'formidable';
+import os from 'os';
+import {setPadHTML} from '../utils/ImportHtml';
+import {setPadRaw} from '../utils/ImportEtherpad';
+import log4js from 'log4js';
+import {aCallAll} from '../../static/js/pluginfw/hooks.js';
const logger = log4js.getLogger('ImportHandler');
// `status` must be a string supported by `importErrorMessage()` in `src/static/js/pad_impexp.js`.
class ImportError extends Error {
+ public status: any;
constructor(status, ...args) {
super(...args);
if (Error.captureStackTrace) Error.captureStackTrace(this, ImportError);
@@ -59,12 +61,12 @@ let converter = null;
let exportExtension = 'htm';
// load abiword only if it is enabled and if soffice is disabled
-if (settings.abiword != null && settings.soffice == null) {
+if (abiword != null && soffice == null) {
converter = require('../utils/Abiword');
}
// load soffice only if it is enabled
-if (settings.soffice != null) {
+if (soffice != null) {
converter = require('../utils/LibreOffice');
exportExtension = 'html';
}
@@ -86,11 +88,11 @@ const doImport = async (req, res, padId, authorId) => {
const form = new Formidable({
keepExtensions: true,
uploadDir: tmpDirectory,
- maxFileSize: settings.importMaxFileSize,
+ maxFileSize: importMaxFileSize,
});
// locally wrapped Promise, since form.parse requires a callback
- let srcFile = await new Promise((resolve, reject) => {
+ let srcFile = await new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err != null) {
logger.warn(`Import failed due to form error: ${err.stack || err}`);
@@ -118,7 +120,7 @@ const doImport = async (req, res, padId, authorId) => {
if (fileEndingUnknown) {
// the file ending is not known
- if (settings.allowUnknownFileEnds === true) {
+ if (allowUnknownFileEnds === true) {
// we need to rename this file with a .txt ending
const oldSrcFile = srcFile;
@@ -132,7 +134,7 @@ const doImport = async (req, res, padId, authorId) => {
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
const context = {srcFile, destFile, fileEnding, padId, ImportError};
- const importHandledByPlugin = (await hooks.aCallAll('import', context)).some((x) => x);
+ const importHandledByPlugin = (await aCallAll('import', context)).some((x) => x);
const fileIsEtherpad = (fileEnding === '.etherpad');
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
const fileIsTXT = (fileEnding === '.txt');
@@ -140,7 +142,7 @@ const doImport = async (req, res, padId, authorId) => {
let directDatabaseAccess = false;
if (fileIsEtherpad) {
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
- const pad = await padManager.getPad(padId, '\n', authorId);
+ const pad = await getPad(padId, '\n', authorId);
const headCount = pad.head;
if (headCount >= 10) {
logger.warn('Aborting direct database import attempt of a pad that already has content');
@@ -148,7 +150,7 @@ const doImport = async (req, res, padId, authorId) => {
}
const text = await fs.readFile(srcFile, 'utf8');
directDatabaseAccess = true;
- await importEtherpad.setPadRaw(padId, text, authorId);
+ await setPadRaw(padId, text, authorId);
}
// convert file to html if necessary
@@ -186,7 +188,7 @@ const doImport = async (req, res, padId, authorId) => {
}
// Use '\n' to avoid the default pad text if the pad doesn't yet exist.
- let pad = await padManager.getPad(padId, '\n', authorId);
+ let pad = await getPad(padId, '\n', authorId);
// read the text
let text;
@@ -205,7 +207,7 @@ const doImport = async (req, res, padId, authorId) => {
if (!directDatabaseAccess) {
if (importHandledByPlugin || useConverter || fileIsHTML) {
try {
- await importHtml.setPadHTML(pad, text, authorId);
+ await setPadHTML(pad, text, authorId);
} catch (err) {
logger.warn(`Error importing, possibly caused by malformed HTML: ${err.stack || err}`);
}
@@ -215,16 +217,16 @@ const doImport = async (req, res, padId, authorId) => {
}
// Load the Pad into memory then broadcast updates to all clients
- padManager.unloadPad(padId);
- pad = await padManager.getPad(padId, '\n', authorId);
- padManager.unloadPad(padId);
+ unloadPad(padId);
+ pad = await getPad(padId, '\n', authorId);
+ unloadPad(padId);
// Direct database access means a pad user should reload the pad and not attempt to receive
// updated pad data.
if (directDatabaseAccess) return true;
// tell clients to update
- await padMessageHandler.updatePadClients(pad);
+ await updatePadClients(pad);
// clean up temporary files
rm(srcFile);
@@ -233,7 +235,7 @@ const doImport = async (req, res, padId, authorId) => {
return false;
};
-exports.doImport = async (req, res, padId, authorId = '') => {
+export const doImport2 = async (req, res, padId, authorId = '') => {
let httpStatus = 200;
let code = 0;
let message = 'ok';
diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.ts
similarity index 82%
rename from src/node/handler/PadMessageHandler.js
rename to src/node/handler/PadMessageHandler.ts
index 9a1885b736e..52f47092612 100644
--- a/src/node/handler/PadMessageHandler.js
+++ b/src/node/handler/PadMessageHandler.ts
@@ -19,34 +19,72 @@
* limitations under the License.
*/
-const AttributeMap = require('../../static/js/AttributeMap');
-const padManager = require('../db/PadManager');
-const Changeset = require('../../static/js/Changeset');
-const ChatMessage = require('../../static/js/ChatMessage');
-const AttributePool = require('../../static/js/AttributePool');
-const AttributeManager = require('../../static/js/AttributeManager');
-const authorManager = require('../db/AuthorManager');
-const {padutils} = require('../../static/js/pad_utils');
-const readOnlyManager = require('../db/ReadOnlyManager');
-const settings = require('../utils/Settings');
+import AttributeMap from '../../static/js/AttributeMap';
+import {getPad} from '../db/PadManager';
+import {
+ builder,
+ checkRep, cloneAText, compose,
+ deserializeOps,
+ follow, 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';
+import AttributeManager from '../../static/js/AttributeManager';
+import {
+ getAuthor,
+ getAuthorColorId,
+ getAuthorName,
+ getColorPalette,
+ setAuthorColorId,
+ setAuthorName
+} from '../db/AuthorManager';
+import {padutils} from '../../static/js/pad_utils';
+import {getIds} from '../db/ReadOnlyManager';
+import {
+ abiwordAvailable,
+ automaticReconnectionTimeout,
+ commitRateLimiting,
+ cookie,
+ disableIPlogging,
+ exportAvailable,
+ indentationOnNewLine,
+ padOptions,
+ padShortcutEnabled,
+ scrollWhenFocusLineIsOutOfViewport,
+ skinName,
+ skinVariants,
+ sofficeAvailable
+} from '../utils/Settings';
+import {parts, plugins} from '../../static/js/pluginfw/plugin_defs.js';
+import log4js from "log4js";
+import {aCallAll, deprecationNotices} from '../../static/js/pluginfw/hooks.js';
+import {createCollection} from '../stats';
+import {strict as assert} from "assert";
+
+import {RateLimiterMemory} from 'rate-limiter-flexible';
+import {userCanModify} from '../hooks/express/webaccess';
+import {ErrorCaused} from "../models/ErrorCaused";
+import {Pad} from "../db/Pad";
+import {SessionInfo} from "../models/SessionInfo";
+import {randomString} from "../utils/randomstring";
+import {identity} from "lodash";
+
const securityManager = require('../db/SecurityManager');
-const plugins = require('../../static/js/pluginfw/plugin_defs.js');
-const log4js = require('log4js');
+
const messageLogger = log4js.getLogger('message');
const accessLogger = log4js.getLogger('access');
-const hooks = require('../../static/js/pluginfw/hooks.js');
-const stats = require('../stats');
-const assert = require('assert').strict;
-const {RateLimiterMemory} = require('rate-limiter-flexible');
-const webaccess = require('../hooks/express/webaccess');
let rateLimiter;
let socketio = null;
-hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
+deprecationNotices.clientReady = 'use the userJoin hook instead';
-const addContextToError = (err, pfx) => {
- const newErr = new Error(`${pfx}${err.message}`, {cause: err});
+const addContextToError = (err: Error, pfx) => {
+ const newErr = new ErrorCaused(`${pfx}${err.message}`, err);
if (Error.captureStackTrace) Error.captureStackTrace(newErr, addContextToError);
// Check for https://github.com/tc39/proposal-error-cause support, available in Node.js >= v16.10.
if (newErr.cause === err) return newErr;
@@ -54,11 +92,11 @@ const addContextToError = (err, pfx) => {
return err;
};
-exports.socketio = () => {
+export const socketiofn = () => {
// 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.
- rateLimiter = new RateLimiterMemory(settings.commitRateLimiting);
+ rateLimiter = new RateLimiterMemory(commitRateLimiting);
};
/**
@@ -79,11 +117,10 @@ 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 = {};
-exports.sessioninfos = sessioninfos;
+export const sessioninfos: SessionInfo = {};
-stats.gauge('totalUsers', () => socketio ? Object.keys(socketio.sockets.sockets).length : 0);
-stats.gauge('activePads', () => {
+createCollection.gauge('totalUsers', () => socketio ? Object.keys(socketio.sockets.sockets).length : 0);
+createCollection.gauge('activePads', () => {
const padIds = new Set();
for (const {padId} of Object.values(sessioninfos)) {
if (!padId) continue;
@@ -96,6 +133,8 @@ stats.gauge('activePads', () => {
* Processes one task at a time per channel.
*/
class Channels {
+ private readonly _exec: (ch, task) => any;
+ private _promiseChains: Map;
/**
* @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.
@@ -135,7 +174,7 @@ const padChannels = new Channels((ch, {socket, message}) => handleUserChanges(so
* This Method is called by server.js to tell the message handler on which socket it should send
* @param socket_io The Socket
*/
-exports.setSocketIO = (socket_io) => {
+export const setSocketIO = (socket_io) => {
socketio = socket_io;
};
@@ -143,17 +182,21 @@ exports.setSocketIO = (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) => {
- stats.meter('connects').mark();
+export const handleConnect = (socket) => {
+ createCollection.meter('connects').mark();
// Initialize sessioninfos for this new session
- sessioninfos[socket.id] = {};
+ sessioninfos[socket.id] = {
+ rev: 0, time: undefined,
+ auth: {padID: undefined, sessionID: undefined, token: undefined},
+ readOnlyPadId: undefined,
+ padId:undefined,readonly:false,author:undefined};
};
/**
* Kicks all sessions from a pad
*/
-exports.kickSessionsFromPad = (padID) => {
+export const kickSessionsFromPad = (padID) => {
if (typeof socketio.sockets.clients !== 'function') return;
// skip if there is nobody on this pad
@@ -167,18 +210,18 @@ exports.kickSessionsFromPad = (padID) => {
* Handles the disconnection of a user
* @param socket the socket.io Socket object for the client
*/
-exports.handleDisconnect = async (socket) => {
- stats.meter('disconnects').mark();
+export const handleDisconnect = async (socket) => {
+ createCollection.meter('disconnects').mark();
const session = sessioninfos[socket.id];
delete sessioninfos[socket.id];
// session.padId can be nullish if the user disconnects before sending CLIENT_READY.
if (!session || !session.author || !session.padId) return;
- const {session: {user} = {}} = socket.client.request;
+ const {session: {user} = {}}: SessionSocketModel = socket.client.request;
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
accessLogger.info('[LEAVE]' +
` pad:${session.padId}` +
` socket:${socket.id}` +
- ` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
+ ` IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
` authorID:${session.author}` +
(user && user.username ? ` username:${user.username}` : ''));
/* eslint-enable prefer-template */
@@ -187,12 +230,12 @@ exports.handleDisconnect = async (socket) => {
data: {
type: 'USER_LEAVE',
userInfo: {
- colorId: await authorManager.getAuthorColorId(session.author),
+ colorId: await getAuthorColorId(session.author),
userId: session.author,
},
},
});
- await hooks.aCallAll('userLeave', {
+ await aCallAll('userLeave', {
...session, // For backwards compatibility.
authorId: session.author,
readOnly: session.readonly,
@@ -205,7 +248,7 @@ exports.handleDisconnect = async (socket) => {
* @param socket the socket.io Socket object for the client
* @param message the message from the client
*/
-exports.handleMessage = async (socket, message) => {
+export const handleMessage = async (socket, message) => {
const env = process.env.NODE_ENV || 'development';
if (env === 'production') {
@@ -214,7 +257,7 @@ exports.handleMessage = async (socket, message) => {
} catch (err) {
messageLogger.warn(`Rate limited IP ${socket.request.ip}. To reduce the amount of rate ` +
'limiting that happens edit the rateLimit values in settings.json');
- stats.meter('rateLimited').mark();
+ createCollection.meter('rateLimited').mark();
socket.json.send({disconnect: 'rateLimited'});
throw err;
}
@@ -235,11 +278,11 @@ exports.handleMessage = async (socket, message) => {
padID: message.padId,
token: message.token,
};
- const padIds = await readOnlyManager.getIds(thisSession.auth.padID);
+ const padIds = await getIds(thisSession.auth.padID);
thisSession.padId = padIds.padId;
thisSession.readOnlyPadId = padIds.readOnlyPadId;
thisSession.readonly =
- padIds.readonly || !webaccess.userCanModify(thisSession.auth.padID, socket.client.request);
+ padIds.readonly || !userCanModify(thisSession.auth.padID, socket.client.request);
}
// Outside of the checks done by this function, message.padId must not be accessed because it is
// too easy to introduce a security vulnerability that allows malicious users to read or modify
@@ -252,12 +295,12 @@ exports.handleMessage = async (socket, message) => {
const auth = thisSession.auth;
if (!auth) {
- const ip = settings.disableIPlogging ? 'ANONYMOUS' : (socket.request.ip || '');
+ const ip = disableIPlogging ? 'ANONYMOUS' : (socket.request.ip || '');
const msg = JSON.stringify(message, null, 2);
throw new Error(`pre-CLIENT_READY message from IP ${ip}: ${msg}`);
}
- const {session: {user} = {}} = socket.client.request;
+ const {session: {user} = {}}:SessionSocketModel = socket.client.request;
const {accessStatus, authorID} =
await securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, user);
if (accessStatus !== 'grant') {
@@ -269,7 +312,7 @@ exports.handleMessage = async (socket, message) => {
throw new Error([
'Author ID changed mid-session. Bad or missing token or sessionID?',
`socket:${socket.id}`,
- `IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}`,
+ `IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}`,
`originalAuthorID:${thisSession.author}`,
`newAuthorID:${authorID}`,
...(user && user.username) ? [`username:${user.username}`] : [],
@@ -295,7 +338,7 @@ exports.handleMessage = async (socket, message) => {
return this.socket;
},
};
- for (const res of await hooks.aCallAll('handleMessageSecurity', context)) {
+ for (const res of await aCallAll('handleMessageSecurity', context)) {
switch (res) {
case true:
padutils.warnDeprecated(
@@ -313,7 +356,7 @@ exports.handleMessage = async (socket, message) => {
}
// 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 aCallAll('handleMessage', context)).some((m) => m == null)) {
return;
}
@@ -331,7 +374,7 @@ exports.handleMessage = async (socket, message) => {
try {
switch (type) {
case 'USER_CHANGES':
- stats.counter('pendingEdits').inc();
+ createCollection.counter('pendingEdits').inc();
await padChannels.enqueue(thisSession.padId, {socket, message});
break;
case 'USERINFO_UPDATE': await handleUserInfoUpdate(socket, message); break;
@@ -372,7 +415,7 @@ exports.handleMessage = async (socket, message) => {
*/
const handleSaveRevisionMessage = async (socket, message) => {
const {padId, author: authorId} = sessioninfos[socket.id];
- const pad = await padManager.getPad(padId, null, authorId);
+ const pad = await getPad(padId, null, authorId);
await pad.addSavedRevision(pad.head, authorId);
};
@@ -383,7 +426,7 @@ const handleSaveRevisionMessage = async (socket, message) => {
* @param msg {Object} the message we're sending
* @param sessionID {string} the socketIO session to which we're sending this message
*/
-exports.handleCustomObjectMessage = (msg, sessionID) => {
+export const handleCustomObjectMessage = (msg, sessionID) => {
if (msg.data.type === 'CUSTOM') {
if (sessionID) {
// a sessionID is targeted: directly to this sessionID
@@ -401,7 +444,7 @@ exports.handleCustomObjectMessage = (msg, sessionID) => {
* @param padID {Pad} the pad to which we're sending this message
* @param msgString {String} the message we're sending
*/
-exports.handleCustomMessage = (padID, msgString) => {
+export const handleCustomMessage = (padID, msgString) => {
const time = Date.now();
const msg = {
type: 'COLLABROOM',
@@ -424,7 +467,7 @@ const handleChatMessage = async (socket, message) => {
// Don't trust the user-supplied values.
chatMessage.time = Date.now();
chatMessage.authorId = authorId;
- await exports.sendChatMessageToPadClients(chatMessage, padId);
+ await sendChatMessageToPadClients(chatMessage, padId);
};
/**
@@ -438,15 +481,15 @@ const handleChatMessage = async (socket, 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.
*/
-exports.sendChatMessageToPadClients = async (mt, puId, text = null, padId = null) => {
+export const sendChatMessageToPadClients = async (mt, puId, text = null, padId = 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);
- await hooks.aCallAll('chatNewMessage', {message, pad, padId});
+ const pad = await getPad(padId, null, message.authorId);
+ await 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);
+ message.displayName = await getAuthorName(message.authorId);
socketio.sockets.in(padId).json.send({
type: 'COLLABROOM',
data: {type: 'CHAT_MESSAGE', message},
@@ -465,7 +508,7 @@ const handleGetChatMessages = async (socket, {data: {start, end}}) => {
const count = end - start;
if (count < 0 || count > 100) throw new Error(`invalid number of messages: ${count}`);
const {padId, author: authorId} = sessioninfos[socket.id];
- const pad = await padManager.getPad(padId, null, authorId);
+ const pad = await getPad(padId, null, authorId);
const chatMessages = await pad.getChatMessages(start, end);
const infoMsg = {
@@ -517,8 +560,8 @@ const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}})
// Tell the authorManager about the new attributes
const p = Promise.all([
- authorManager.setAuthorColorId(author, colorId),
- authorManager.setAuthorName(author, name),
+ setAuthorColorId(author, colorId),
+ setAuthorName(author, name),
]);
const padId = session.padId;
@@ -555,7 +598,7 @@ const handleUserInfoUpdate = async (socket, {data: {userInfo: {name, colorId}}})
*/
const handleUserChanges = async (socket, message) => {
// This one's no longer pending, as we're gonna process it now
- stats.counter('pendingEdits').dec();
+ createCollection.counter('pendingEdits').dec();
// The client might disconnect between our callbacks. We should still
// finish processing the changeset, so keep a reference to the session.
@@ -567,20 +610,20 @@ const handleUserChanges = async (socket, message) => {
if (!thisSession) throw new Error('client disconnected');
// Measure time to process edit
- const stopWatch = stats.timer('edits').start();
+ const stopWatch = createCollection.timer('edits').start();
try {
const {data: {baseRev, apool, changeset}} = message;
if (baseRev == null) throw new Error('missing baseRev');
if (apool == null) throw new Error('missing apool');
if (changeset == null) throw new Error('missing changeset');
const wireApool = (new AttributePool()).fromJsonable(apool);
- const pad = await padManager.getPad(thisSession.padId, null, thisSession.author);
+ const pad = await getPad(thisSession.padId, null, thisSession.author);
// Verify that the changeset has valid syntax and is in canonical form
- Changeset.checkRep(changeset);
+ checkRep(changeset);
// Validate all added 'author' attribs to be the same value as the current user
- for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
+ for (const op of deserializeOps(unpack(changeset).ops)) {
// + can add text with attribs
// = can change or add attribs
// - can have attribs, but they are discarded and don't show up in the attribs -
@@ -599,7 +642,7 @@ const handleUserChanges = async (socket, message) => {
// ex. adoptChangesetAttribs
// Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
- let rebasedChangeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool);
+ let rebasedChangeset = moveOpsToNewPool(changeset, wireApool, pad.pool);
// ex. applyUserChanges
let r = baseRev;
@@ -612,21 +655,21 @@ const handleUserChanges = async (socket, message) => {
const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r);
if (changeset === c && thisSession.author === authorId) {
// Assume this is a retransmission of an already applied changeset.
- rebasedChangeset = Changeset.identity(Changeset.unpack(changeset).oldLen);
+ rebasedChangeset = identity(unpack(changeset).oldLen);
}
// At this point, both "c" (from the pad) and "changeset" (from the
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// and can be applied after "c".
- rebasedChangeset = Changeset.follow(c, rebasedChangeset, false, pad.pool);
+ rebasedChangeset = follow(c, rebasedChangeset, false, pad.pool);
}
const prevText = pad.text();
- if (Changeset.oldLen(rebasedChangeset) !== prevText.length) {
+ if (oldLen(rebasedChangeset) !== prevText.length) {
throw new Error(
`Can't apply changeset ${rebasedChangeset} with oldLen ` +
- `${Changeset.oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
+ `${oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
}
const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);
@@ -641,7 +684,7 @@ const handleUserChanges = async (socket, message) => {
// Make sure the pad always ends with an empty line.
if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) {
- const nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, '\n');
+ const nlChangeset = makeSplice(pad.text(), pad.text().length - 1, 0, '\n');
await pad.appendRevision(nlChangeset, thisSession.author);
}
@@ -651,10 +694,10 @@ const handleUserChanges = async (socket, message) => {
socket.json.send({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) {
socket.json.send({disconnect: 'badChangeset'});
- stats.meter('failedChangesets').mark();
+ createCollection.meter('failedChangesets').mark();
messageLogger.warn(`Failed to apply USER_CHANGES from author ${thisSession.author} ` +
`(socket ${socket.id}) on pad ${thisSession.padId}: ${err.stack || err}`);
} finally {
@@ -662,7 +705,7 @@ const handleUserChanges = async (socket, message) => {
}
};
-exports.updatePadClients = async (pad) => {
+export const updatePadClients = async (pad) => {
// skip this if no-one is on this pad
const roomSockets = _getRoomSockets(pad.id);
if (roomSockets.length === 0) return;
@@ -696,7 +739,7 @@ exports.updatePadClients = async (pad) => {
const revChangeset = revision.changeset;
const currentTime = revision.meta.timestamp;
- const forWire = Changeset.prepareForWire(revChangeset, pad.pool);
+ const forWire = prepareForWire(revChangeset, pad.pool);
const msg = {
type: 'COLLABROOM',
data: {
@@ -731,7 +774,7 @@ const _correctMarkersInPad = (atext, apool) => {
// that aren't at the start of a line
const badMarkers = [];
let offset = 0;
- for (const op of Changeset.deserializeOps(atext.attribs)) {
+ for (const op of deserializeOps(atext.attribs)) {
const attribs = AttributeMap.fromString(op.attribs, apool);
const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
if (hasMarker) {
@@ -753,17 +796,18 @@ const _correctMarkersInPad = (atext, apool) => {
// create changeset that removes these bad markers
offset = 0;
- const builder = Changeset.builder(text.length);
+ const builder2 = builder(text.length);
badMarkers.forEach((pos) => {
- builder.keepText(text.substring(offset, pos));
- builder.remove(1);
+ builder2.keepText(text.substring(offset, pos));
+ builder2.remove(1);
offset = pos + 1;
});
- return builder.toString();
+ return builder2.toString();
};
+export let clientVars:any
/**
* Handles a CLIENT_READY. A CLIENT_READY is the first message from the client
* to the server. The Client sends his token
@@ -776,7 +820,7 @@ const handleClientReady = async (socket, message) => {
if (sessionInfo == null) throw new Error('client disconnected');
assert(sessionInfo.author);
- await hooks.aCallAll('clientReady', message); // Deprecated due to awkward context.
+ await aCallAll('clientReady', message); // Deprecated due to awkward context.
let {colorId: authorColorId, name: authorName} = message.userInfo || {};
if (authorColorId && !/^#(?:[0-9A-F]{3}){1,2}$/i.test(authorColorId)) {
@@ -784,13 +828,13 @@ const handleClientReady = async (socket, message) => {
authorColorId = null;
}
await Promise.all([
- authorName && authorManager.setAuthorName(sessionInfo.author, authorName),
- authorColorId && authorManager.setAuthorColorId(sessionInfo.author, authorColorId),
+ authorName && setAuthorName(sessionInfo.author, authorName),
+ authorColorId && setAuthorColorId(sessionInfo.author, authorColorId),
]);
- ({colorId: authorColorId, name: authorName} = await authorManager.getAuthor(sessionInfo.author));
+ ({colorId: authorColorId, name: authorName} = await getAuthor(sessionInfo.author));
// load the pad-object from the database
- const pad = await padManager.getPad(sessionInfo.padId, null, sessionInfo.author);
+ const pad = await createCollection.getPad(sessionInfo.padId, null, sessionInfo.author);
// these db requests all need the pad object (timestamp of latest revision, author data)
const authors = pad.getAllAuthors();
@@ -801,7 +845,7 @@ const handleClientReady = async (socket, message) => {
// get all author data out of the database (in parallel)
const historicalAuthorData = {};
await Promise.all(authors.map(async (authorId) => {
- const author = await authorManager.getAuthor(authorId);
+ const author = await getAuthor(authorId);
if (!author) {
messageLogger.error(`There is no author for authorId: ${authorId}. ` +
'This is possibly related to https://github.com/ether/etherpad-lite/issues/2802');
@@ -825,18 +869,18 @@ const handleClientReady = async (socket, message) => {
const sinfo = sessioninfos[otherSocket.id];
if (sinfo && sinfo.author === sessionInfo.author) {
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
- sessioninfos[otherSocket.id] = {};
+ sessioninfos[otherSocket.id] = undefined
otherSocket.leave(sessionInfo.padId);
otherSocket.json.send({disconnect: 'userdup'});
}
}
- const {session: {user} = {}} = socket.client.request;
+ const {session: {user} = {}}:SessionSocketModel = socket.client.request;
/* eslint-disable prefer-template -- it doesn't support breaking across multiple lines */
accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` +
` pad:${sessionInfo.padId}` +
` socket:${socket.id}` +
- ` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
+ ` IP:${disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` +
` authorID:${sessionInfo.author}` +
(user && user.username ? ` username:${user.username}` : ''));
/* eslint-enable prefer-template */
@@ -884,7 +928,7 @@ const handleClientReady = async (socket, message) => {
// return pending changesets
for (const r of revisionsNeeded) {
- const forWire = Changeset.prepareForWire(changesets[r].changeset, pad.pool);
+ const forWire = prepareForWire(changesets[r].changeset, pad.pool);
const wireMsg = {type: 'COLLABROOM',
data: {type: 'CLIENT_RECONNECT',
headRev: pad.getHeadRevisionNumber(),
@@ -909,8 +953,8 @@ const handleClientReady = async (socket, message) => {
let apool;
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
try {
- atext = Changeset.cloneAText(pad.atext);
- const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
+ atext = cloneAText(pad.atext);
+ const attribsForWire = prepareForWire(atext.attribs, pad.pool);
apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated;
} catch (e) {
@@ -921,14 +965,14 @@ const handleClientReady = async (socket, message) => {
// 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...
- const clientVars = {
- skinName: settings.skinName,
- skinVariants: settings.skinVariants,
- randomVersionString: settings.randomVersionString,
+ clientVars = {
+ skinName: skinName,
+ skinVariants: skinVariants,
+ randomVersionString: randomString(4),
accountPrivs: {
maxRevisions: 100,
},
- automaticReconnectionTimeout: settings.automaticReconnectionTimeout,
+ automaticReconnectionTimeout: automaticReconnectionTimeout,
initialRevisionList: [],
initialOptions: {},
savedRevisions: pad.getSavedRevisions(),
@@ -941,12 +985,12 @@ const handleClientReady = async (socket, message) => {
rev: pad.getHeadRevisionNumber(),
time: currentTime,
},
- colorPalette: authorManager.getColorPalette(),
+ colorPalette: getColorPalette(),
clientIp: '127.0.0.1',
userColor: authorColorId,
padId: sessionInfo.auth.padID,
- padOptions: settings.padOptions,
- padShortcutEnabled: settings.padShortcutEnabled,
+ padOptions: padOptions,
+ padShortcutEnabled: padShortcutEnabled,
initialTitle: `Pad: ${sessionInfo.auth.padID}`,
opts: {},
// tell the client the number of the latest chat-message, which will be
@@ -956,29 +1000,30 @@ const handleClientReady = async (socket, message) => {
readOnlyId: sessionInfo.readOnlyPadId,
readonly: sessionInfo.readonly,
serverTimestamp: Date.now(),
- sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
+ sessionRefreshInterval: cookie.sessionRefreshInterval,
userId: sessionInfo.author,
- abiwordAvailable: settings.abiwordAvailable(),
- sofficeAvailable: settings.sofficeAvailable(),
- exportAvailable: settings.exportAvailable(),
+ abiwordAvailable: abiwordAvailable(),
+ sofficeAvailable: sofficeAvailable(),
+ exportAvailable: exportAvailable(),
plugins: {
- plugins: plugins.plugins,
- parts: plugins.parts,
+ plugins: plugins,
+ parts: parts,
},
- indentationOnNewLine: settings.indentationOnNewLine,
+ indentationOnNewLine: indentationOnNewLine,
scrollWhenFocusLineIsOutOfViewport: {
percentage: {
editionAboveViewport:
- settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
+ scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
editionBelowViewport:
- settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
+ scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
},
- duration: settings.scrollWhenFocusLineIsOutOfViewport.duration,
+ duration: scrollWhenFocusLineIsOutOfViewport.duration,
scrollWhenCaretIsInTheLastLineOfViewport:
- settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
+ scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
percentageToScrollWhenUserPressesArrowUp:
- settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
+ scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
},
+ userName: undefined,
initialChangesets: [], // FIXME: REMOVE THIS SHIT
};
@@ -988,7 +1033,7 @@ const handleClientReady = async (socket, message) => {
}
// call the clientVars-hook so plugins can modify them before they get sent to the client
- const messages = await hooks.aCallAll('clientVars', {clientVars, pad, socket});
+ const messages = await aCallAll('clientVars', {clientVars, pad, socket});
// combine our old object with the new attributes from the hook
for (const msg of messages) {
@@ -1034,7 +1079,7 @@ const handleClientReady = async (socket, message) => {
if (authorId == null) return;
// reuse previously created cache of author's data
- const authorInfo = historicalAuthorData[authorId] || await authorManager.getAuthor(authorId);
+ const authorInfo = historicalAuthorData[authorId] || await getAuthor(authorId);
if (authorInfo == null) {
messageLogger.error(
`Author ${authorId} connected via socket.io session ${roomSocket.id} is missing from ` +
@@ -1059,7 +1104,7 @@ const handleClientReady = async (socket, message) => {
socket.json.send(msg);
}));
- await hooks.aCallAll('userJoin', {
+ await aCallAll('userJoin', {
authorId: sessionInfo.author,
displayName: authorName,
padId: sessionInfo.padId,
@@ -1079,7 +1124,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
if (requestID == null) throw new Error('mising requestID');
const end = start + (100 * granularity);
const {padId, author: authorId} = sessioninfos[socket.id];
- const pad = await padManager.getPad(padId, null, authorId);
+ const pad = await createCollection.getPad(padId, null, authorId);
const data = await getChangesetInfo(pad, start, end, granularity);
data.requestID = requestID;
socket.json.send({type: 'CHANGESET_REQ', data});
@@ -1089,7 +1134,7 @@ const handleChangesetRequest = async (socket, {data: {granularity, start, reques
* 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, startNum, endNum, granularity) => {
+const getChangesetInfo = async (pad: Pad, startNum: number, endNum: number, granularity: number) => {
const headRevision = pad.getHeadRevisionNumber();
// calculate the last full endnum
@@ -1120,8 +1165,7 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
getPadLines(pad, startNum - 1),
// Get all needed composite Changesets.
...compositesChangesetNeeded.map(async (item) => {
- const changeset = await composePadChangesets(pad, item.start, item.end);
- composedChangesets[`${item.start}/${item.end}`] = changeset;
+ composedChangesets[`${item.start}/${item.end}`] = await composePadChangesets(pad, item.start, item.end);
}),
// Get all needed revision Dates.
...revTimesNeeded.map(async (revNum) => {
@@ -1141,13 +1185,13 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
if (compositeEnd > endNum || compositeEnd > headRevision + 1) break;
const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`];
- const backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
+ const backwards = inverse(forwards, lines.textlines, lines.alines, pad.apool());
- Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
- Changeset.mutateTextLines(forwards, lines.textlines);
+ mutateAttributionLines(forwards, lines.alines, pad.apool());
+ mutateTextLines(forwards, lines.textlines);
- const forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
- const backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
+ const forwards2 = moveOpsToNewPool(forwards, pad.apool(), apool);
+ const backwards2 = moveOpsToNewPool(backwards, pad.apool(), apool);
const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1];
const t2 = revisionDate[compositeEnd - 1];
@@ -1159,7 +1203,9 @@ const getChangesetInfo = async (pad, startNum, endNum, granularity) => {
return {forwardsChangesets, backwardsChangesets,
apool: apool.toJsonable(), actualEndNum: endNum,
- timeDeltas, start: startNum, granularity};
+ timeDeltas, start: startNum, granularity, requestID: undefined
+
+ };
};
/**
@@ -1173,12 +1219,12 @@ const getPadLines = async (pad, revNum) => {
if (revNum >= 0) {
atext = await pad.getInternalRevisionAText(revNum);
} else {
- atext = Changeset.makeAText('\n');
+ atext = makeAText('\n');
}
return {
- textlines: Changeset.splitTextLines(atext.text),
- alines: Changeset.splitAttributionLines(atext.attribs, atext.text),
+ textlines: splitTextLines(atext.text),
+ alines: splitAttributionLines(atext.attribs, atext.text),
};
};
@@ -1213,7 +1259,7 @@ const composePadChangesets = async (pad, startNum, endNum) => {
for (r = startNum + 1; r < endNum; r++) {
const cs = changesets[r];
- changeset = Changeset.compose(changeset, cs, pool);
+ changeset = compose(changeset, cs, pool);
}
return changeset;
} catch (e) {
@@ -1238,21 +1284,21 @@ const _getRoomSockets = (padID) => {
/**
* Get the number of users in a pad
*/
-exports.padUsersCount = (padID) => ({
+export const padUsersCount = (padID) => ({
padUsersCount: _getRoomSockets(padID).length,
});
/**
* Get the list of users in a pad
*/
-exports.padUsers = async (padID) => {
+export const padUsers = async (padID) => {
const padUsers = [];
// iterate over all clients (in parallel)
await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => {
const s = sessioninfos[roomSocket.id];
if (s) {
- const author = await authorManager.getAuthor(s.author);
+ const author = await getAuthor(s.author);
// Fixes: https://github.com/ether/etherpad-lite/issues/4120
// On restart author might not be populated?
if (author) {
@@ -1263,6 +1309,4 @@ exports.padUsers = async (padID) => {
}));
return {padUsers};
-};
-
-exports.sessioninfos = sessioninfos;
+}
diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.ts
similarity index 80%
rename from src/node/handler/SocketIORouter.js
rename to src/node/handler/SocketIORouter.ts
index 863401aceb4..9f7875566b1 100644
--- a/src/node/handler/SocketIORouter.js
+++ b/src/node/handler/SocketIORouter.ts
@@ -20,9 +20,10 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-const settings = require('../utils/Settings');
-const stats = require('../stats');
+import log4js from 'log4js';
+import {disableIPlogging} from "../utils/Settings";
+
+import {createCollection} from '../stats';
const logger = log4js.getLogger('socket.io');
@@ -38,22 +39,22 @@ let io;
/**
* adds a component
*/
-exports.addComponent = (moduleName, module) => {
- if (module == null) return exports.deleteComponent(moduleName);
+export const addComponent = (moduleName, module) => {
+ if (module == null) return deleteComponent(moduleName);
components[moduleName] = module;
module.setSocketIO(io);
};
-exports.deleteComponent = (moduleName) => { delete components[moduleName]; };
+export const deleteComponent = (moduleName) => { delete components[moduleName]; };
/**
* sets the socket.io and adds event functions for routing
*/
-exports.setSocketIO = (_io) => {
+export const setSocketIO = (_io) => {
io = _io;
io.sockets.on('connection', (socket) => {
- const ip = settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
+ const ip = disableIPlogging ? 'ANONYMOUS' : socket.request.ip;
logger.debug(`${socket.id} connected from IP ${ip}`);
// wrap the original send function to log the messages
@@ -68,14 +69,14 @@ exports.setSocketIO = (_io) => {
components[i].handleConnect(socket);
}
- socket.on('message', (message, ack = () => {}) => (async () => {
+ socket.on('message', (message, ack = (p: { name: any; message: any }) => {}) => (async () => {
if (!message.component || !components[message.component]) {
throw new Error(`unknown message component: ${message.component}`);
}
logger.debug(`from ${socket.id}:`, message);
return await components[message.component].handleMessage(socket, message);
})().then(
- (val) => ack(null, val),
+ (val) => ack({name: val.name, message: val.message}),
(err) => {
logger.error(
`Error handling ${message.component} message from ${socket.id}: ${err.stack || err}`);
@@ -88,7 +89,7 @@ exports.setSocketIO = (_io) => {
// when the last user disconnected. If your activePads is 0 and totalUsers is 0
// you can say, if there has been no active pads or active users for 10 minutes
// this instance can be brought out of a scaling cluster.
- stats.gauge('lastDisconnect', () => Date.now());
+ createCollection.gauge('lastDisconnect', () => Date.now());
// tell all components about this disconnect
for (const i of Object.keys(components)) {
components[i].handleDisconnect(socket);
diff --git a/src/node/hooks/express.js b/src/node/hooks/express.ts
similarity index 64%
rename from src/node/hooks/express.js
rename to src/node/hooks/express.ts
index 9c42fd6d867..ef43d451685 100644
--- a/src/node/hooks/express.js
+++ b/src/node/hooks/express.ts
@@ -1,35 +1,58 @@
'use strict';
-const _ = require('underscore');
-const cookieParser = require('cookie-parser');
-const events = require('events');
-const express = require('express');
-const expressSession = require('express-session');
-const fs = require('fs');
-const hooks = require('../../static/js/pluginfw/hooks');
-const log4js = require('log4js');
-const SessionStore = require('../db/SessionStore');
-const settings = require('../utils/Settings');
-const stats = require('../stats');
-const util = require('util');
-const webaccess = require('./express/webaccess');
+import _ from 'underscore';
+import cookieParser from 'cookie-parser';
+import events from "events";
+
+import express from "express";
+
+import fs from "fs";
+
+import expressSession from "express-session";
+
+import {aCallAll} from "../../static/js/pluginfw/hooks";
+
+import log4js from "log4js";
+
+import SessionStore from "../db/SessionStore";
+
+import {
+ cookie,
+ exposeVersion,
+ getEpVersion,
+ getGitCommit,
+ ip,
+ loglevel,
+ port,
+ sessionKey,
+ ssl, sslKeys,
+ trustProxy,
+ users
+} from "../utils/Settings";
+
+import {createCollection} from "../stats";
+
+import util from "util";
+
+import {checkAccess, checkAccess2} from "./express/webaccess";
+import {Socket} from "net";
const logger = log4js.getLogger('http');
let serverName;
let sessionStore;
-const sockets = new Set();
+const sockets = new Set();
const socketsEvents = new events.EventEmitter();
-const startTime = stats.settableGauge('httpStartTime');
-
-exports.server = null;
+const startTime = createCollection.settableGauge('httpStartTime');
+export let server = null;
+export let sessionMiddleware;
const closeServer = async () => {
- if (exports.server != null) {
+ if (server != null) {
logger.info('Closing HTTP server...');
// Call exports.server.close() to reject new connections but don't await just yet because the
// Promise won't resolve until all preexisting connections are closed.
- const p = util.promisify(exports.server.close.bind(exports.server))();
- await hooks.aCallAll('expressCloseServer');
+ const p = util.promisify(server.close.bind(server))();
+ await aCallAll('expressCloseServer');
// Give existing connections some time to close on their own before forcibly terminating. The
// time should be long enough to avoid interrupting most preexisting transmissions but short
// enough to avoid a noticeable outage.
@@ -47,7 +70,7 @@ const closeServer = async () => {
}
await p;
clearTimeout(timeout);
- exports.server = null;
+ server = null;
startTime.setValue(0);
logger.info('HTTP server closed');
}
@@ -55,71 +78,73 @@ const closeServer = async () => {
sessionStore = null;
};
-exports.createServer = async () => {
- console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
+export const createServer = async () => {
+ logger.info('Report bugs at https://github.com/ether/etherpad-lite/issues');
- serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
+ serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`;
- console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
+ logger.info(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`);
- await exports.restartServer();
+ await restartServer();
- if (settings.ip === '') {
+ if (ip.length===0) {
// using Unix socket for connectivity
- console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
+ logger.info(`You can access your Etherpad instance using the Unix socket at ${port}`);
} else {
- console.log(`You can access your Etherpad instance at http://${settings.ip}:${settings.port}/`);
+ logger.info(`You can access your Etherpad instance at http://${ip}:${port}/`);
}
- if (!_.isEmpty(settings.users)) {
- console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`);
+ if (!_.isEmpty(users)) {
+ logger.info(`The plugin admin page is at http://${ip}:${port}/admin/plugins`);
} else {
- console.warn('Admin username and password not set in settings.json. ' +
+ logger.info('Admin username and password not set in settings.json. ' +
'To access admin please uncomment and edit "users" in settings.json');
}
const env = process.env.NODE_ENV || 'development';
if (env !== 'production') {
- console.warn('Etherpad is running in Development mode. This mode is slower for users and ' +
+ logger.warn('Etherpad is running in Development mode. This mode is slower for users and ' +
'less secure than production mode. You should set the NODE_ENV environment ' +
'variable to production by using: export NODE_ENV=production');
}
};
+export const app = express();
+
+import http from 'http'
+import https from 'https'
-exports.restartServer = async () => {
+export const restartServer = async () => {
await closeServer();
- const app = express(); // New syntax for express v3
+ // New syntax for express v3
- if (settings.ssl) {
+ if (ssl) {
console.log('SSL -- enabled');
- console.log(`SSL -- server key file: ${settings.ssl.key}`);
- console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
+ console.log(`SSL -- server key file: ${sslKeys.key}`);
+ console.log(`SSL -- Certificate Authority's certificate file: ${sslKeys.cert}`);
const options = {
- key: fs.readFileSync(settings.ssl.key),
- cert: fs.readFileSync(settings.ssl.cert),
+ key: fs.readFileSync(sslKeys.key),
+ cert: fs.readFileSync(sslKeys.cert),
+ ca: undefined
};
- if (settings.ssl.ca) {
+ if (sslKeys.ca) {
options.ca = [];
- for (let i = 0; i < settings.ssl.ca.length; i++) {
- const caFileName = settings.ssl.ca[i];
+ for (let i = 0; i < sslKeys.ca.length; i++) {
+ const caFileName = sslKeys.ca[i];
options.ca.push(fs.readFileSync(caFileName));
}
}
-
- const https = require('https');
- exports.server = https.createServer(options, app);
+ server = https.createServer(options, app);
} else {
- const http = require('http');
- exports.server = http.createServer(app);
+ server = http.createServer(app);
}
app.use((req, res, next) => {
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
- if (settings.ssl) {
+ if (ssl) {
// we use SSL
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
@@ -138,14 +163,14 @@ exports.restartServer = async () => {
res.header('Referrer-Policy', 'same-origin');
// send git version in the Server response header if exposeVersion is true.
- if (settings.exposeVersion) {
+ if (exposeVersion) {
res.header('Server', serverName);
}
next();
});
- if (settings.trustProxy) {
+ if (trustProxy) {
/*
* If 'trust proxy' === true, the client’s IP address in req.ip will be the
* left-most entry in the X-Forwarded-* header.
@@ -157,8 +182,10 @@ exports.restartServer = async () => {
// Measure response time
app.use((req, res, next) => {
- const stopWatch = stats.timer('httpRequests').start();
+ const stopWatch = createCollection.timer('httpRequests').start();
const sendFn = res.send.bind(res);
+ // FIXME Check if this is still needed
+ // @ts-ignore
res.send = (...args) => { stopWatch.end(); sendFn(...args); };
next();
});
@@ -167,20 +194,20 @@ exports.restartServer = async () => {
// starts listening to requests as reported in issue #158. Not installing the log4js connect
// logger when the log level has a higher severity than INFO since it would not log at that level
// anyway.
- if (!(settings.loglevel === 'WARN' && settings.loglevel === 'ERROR')) {
+ if (!(loglevel === 'WARN') && loglevel === 'ERROR') {
app.use(log4js.connectLogger(logger, {
- level: log4js.levels.DEBUG,
+ level: loglevel,
format: ':status, :method :url',
}));
}
- app.use(cookieParser(settings.sessionKey, {}));
+ app.use(cookieParser(sessionKey, {}));
- sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
- exports.sessionMiddleware = expressSession({
+ sessionStore = new SessionStore(cookie.sessionRefreshInterval);
+sessionMiddleware = expressSession({
propagateTouch: true,
rolling: true,
- secret: settings.sessionKey,
+ secret: sessionKey,
store: sessionStore,
resave: false,
saveUninitialized: false,
@@ -188,8 +215,8 @@ exports.restartServer = async () => {
// cleaner :)
name: 'express_sid',
cookie: {
- maxAge: settings.cookie.sessionLifetime || null, // Convert 0 to null.
- sameSite: settings.cookie.sameSite,
+ maxAge: cookie.sessionLifetime || null, // Convert 0 to null.
+ sameSite: cookie.sameSite,
// The automatic express-session mechanism for determining if the application is being served
// over ssl is similar to the one used for setting the language cookie, which check if one of
@@ -214,16 +241,16 @@ exports.restartServer = async () => {
// Give plugins an opportunity to install handlers/middleware before the express-session
// middleware. This allows plugins to avoid creating an express-session record in the database
// when it is not needed (e.g., public static content).
- await hooks.aCallAll('expressPreSession', {app});
- app.use(exports.sessionMiddleware);
+ await aCallAll('expressPreSession', {app});
+ app.use(sessionMiddleware);
- app.use(webaccess.checkAccess);
+ app.use(checkAccess2);
await Promise.all([
- hooks.aCallAll('expressConfigure', {app}),
- hooks.aCallAll('expressCreateServer', {app, server: exports.server}),
+ aCallAll('expressConfigure', {app}),
+ aCallAll('expressCreateServer', {app, server: server}),
]);
- exports.server.on('connection', (socket) => {
+ server.on('connection', (socket) => {
sockets.add(socket);
socketsEvents.emit('updated');
socket.on('close', () => {
@@ -231,11 +258,11 @@ exports.restartServer = async () => {
socketsEvents.emit('updated');
});
});
- await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
+ await util.promisify(server.listen).bind(server)(port, ip);
startTime.setValue(Date.now());
logger.info('HTTP server listening for connections');
};
-exports.shutdown = async (hookName, context) => {
+export const shutdown = async (hookName, context) => {
await closeServer();
};
diff --git a/src/node/hooks/express/admin.js b/src/node/hooks/express/admin.js
deleted file mode 100644
index 7636983327b..00000000000
--- a/src/node/hooks/express/admin.js
+++ /dev/null
@@ -1,10 +0,0 @@
-'use strict';
-const eejs = require('../../eejs');
-
-exports.expressCreateServer = (hookName, args, cb) => {
- args.app.get('/admin', (req, res) => {
- if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
- res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
- });
- return cb();
-};
diff --git a/src/node/hooks/express/admin.ts b/src/node/hooks/express/admin.ts
new file mode 100644
index 00000000000..d55f49ea572
--- /dev/null
+++ b/src/node/hooks/express/admin.ts
@@ -0,0 +1,10 @@
+'use strict';
+import {required} from '../../eejs';
+
+export const expressCreateServer = (hookName:string, args, cb) => {
+ args.app.get('/admin', (req, res) => {
+ if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
+ res.send(required('ep_etherpad-lite/templates/admin/index.html', {req}));
+ });
+ return cb();
+};
diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.ts
similarity index 57%
rename from src/node/hooks/express/adminplugins.js
rename to src/node/hooks/express/adminplugins.ts
index 543f04c0fb9..03a62655f55 100644
--- a/src/node/hooks/express/adminplugins.js
+++ b/src/node/hooks/express/adminplugins.ts
@@ -1,33 +1,38 @@
'use strict';
-const eejs = require('../../eejs');
-const settings = require('../../utils/Settings');
-const installer = require('../../../static/js/pluginfw/installer');
-const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
-const plugins = require('../../../static/js/pluginfw/plugins');
-const semver = require('semver');
-const UpdateCheck = require('../../utils/UpdateCheck');
-
-exports.expressCreateServer = (hookName, args, cb) => {
+import {required} from '../../eejs';
+import {getEpVersion, getGitCommit} from "../../utils/Settings";
+
+import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
+
+import {plugins} from "../../../static/js/pluginfw/plugin_defs";
+
+import {formatHooks, formatParts, formatPlugins} from "../../../static/js/pluginfw/plugins";
+
+import semver from "semver";
+
+import UpdateCheck from "../../utils/UpdateCheck";
+
+export const expressCreateServer = (hookName, args, cb) => {
args.app.get('/admin/plugins', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
- plugins: pluginDefs.plugins,
+ res.send(required('ep_etherpad-lite/templates/admin/plugins.html', {
+ plugins: plugins,
req,
errors: [],
}));
});
args.app.get('/admin/plugins/info', (req, res) => {
- const gitCommit = settings.getGitCommit();
- const epVersion = settings.getEpVersion();
+ const gitCommit = getGitCommit();
+ const epVersion = getEpVersion();
- res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
+ res.send(required('ep_etherpad-lite/templates/admin/plugins-info.html', {
gitCommit,
epVersion,
- installedPlugins: `${plugins.formatPlugins().replace(/, /g, '\n')}`,
- installedParts: `${plugins.formatParts()}`,
- installedServerHooks: `${plugins.formatHooks('hooks', true)}
`,
- installedClientHooks: `${plugins.formatHooks('client_hooks', true)}
`,
+ installedPlugins: `${formatPlugins().replace(/, /g, '\n')}`,
+ installedParts: `${formatParts()}`,
+ installedServerHooks: `${formatHooks('hooks', true)}
`,
+ installedClientHooks: `${formatHooks('client_hooks', true)}
`,
latestVersion: UpdateCheck.getLatestVersion(),
req,
}));
@@ -36,16 +41,16 @@ exports.expressCreateServer = (hookName, args, cb) => {
return cb();
};
-exports.socketio = (hookName, args, cb) => {
+export const socketio = (hookName, args, cb) => {
const io = args.io.of('/pluginfw/installer');
io.on('connection', (socket) => {
- const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
+ const {session: {user: {is_admin: isAdmin} = {}} = {}}:SessionSocketModel = socket.conn.request;
if (!isAdmin) return;
socket.on('getInstalled', (query) => {
// send currently installed plugins
const installed =
- Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
+ Object.keys(plugins).map((plugin) => plugins[plugin].package);
socket.emit('results:installed', {installed});
});
@@ -53,13 +58,13 @@ exports.socketio = (hookName, args, cb) => {
socket.on('checkUpdates', async () => {
// Check plugins for updates
try {
- const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
+ const results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
- const updatable = Object.keys(pluginDefs.plugins).filter((plugin) => {
+ const updatable = Object.keys(plugins).filter((plugin) => {
if (!results[plugin]) return false;
const latestVersion = results[plugin].version;
- const currentVersion = pluginDefs.plugins[plugin].package.version;
+ const currentVersion = plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion);
});
@@ -74,7 +79,7 @@ exports.socketio = (hookName, args, cb) => {
socket.on('getAvailable', async (query) => {
try {
- const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
+ const results = await getAvailablePlugins(/* maxCacheAge:*/ false);
socket.emit('results:available', results);
} catch (er) {
console.error(er);
@@ -84,10 +89,10 @@ exports.socketio = (hookName, args, cb) => {
socket.on('search', async (query) => {
try {
- const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
+ const results = await search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
let res = Object.keys(results)
.map((pluginName) => results[pluginName])
- .filter((plugin) => !pluginDefs.plugins[plugin.name]);
+ .filter((plugin) => !plugins[plugin.name]);
res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset + query.limit);
socket.emit('results:search', {results: res, query});
@@ -99,7 +104,7 @@ exports.socketio = (hookName, args, cb) => {
});
socket.on('install', (pluginName) => {
- installer.install(pluginName, (err) => {
+ install(pluginName, (err) => {
if (err) console.warn(err.stack || err.toString());
socket.emit('finished:install', {
@@ -111,7 +116,7 @@ exports.socketio = (hookName, args, cb) => {
});
socket.on('uninstall', (pluginName) => {
- installer.uninstall(pluginName, (err) => {
+ uninstall(pluginName, (err) => {
if (err) console.warn(err.stack || err.toString());
socket.emit('finished:uninstall', {plugin: pluginName, error: err ? err.message : null});
diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js
deleted file mode 100644
index 792801dc72e..00000000000
--- a/src/node/hooks/express/adminsettings.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-
-const eejs = require('../../eejs');
-const fsp = require('fs').promises;
-const hooks = require('../../../static/js/pluginfw/hooks');
-const plugins = require('../../../static/js/pluginfw/plugins');
-const settings = require('../../utils/Settings');
-
-exports.expressCreateServer = (hookName, {app}) => {
- app.get('/admin/settings', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
- req,
- settings: '',
- errors: [],
- }));
- });
-};
-
-exports.socketio = (hookName, {io}) => {
- io.of('/settings').on('connection', (socket) => {
- const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request;
- if (!isAdmin) return;
-
- socket.on('load', async (query) => {
- let data;
- try {
- data = await fsp.readFile(settings.settingsFilename, 'utf8');
- } catch (err) {
- return console.log(err);
- }
- // if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
- if (settings.showSettingsInAdminPage === false) {
- socket.emit('settings', {results: 'NOT_ALLOWED'});
- } else {
- socket.emit('settings', {results: data});
- }
- });
-
- socket.on('saveSettings', async (newSettings) => {
- await fsp.writeFile(settings.settingsFilename, newSettings);
- socket.emit('saveprogress', 'saved');
- });
-
- socket.on('restartServer', async () => {
- console.log('Admin request to restart server through a socket on /admin/settings');
- settings.reloadSettings();
- await plugins.update();
- await hooks.aCallAll('loadSettings', {settings});
- await hooks.aCallAll('restartServer');
- });
- });
-};
diff --git a/src/node/hooks/express/adminsettings.ts b/src/node/hooks/express/adminsettings.ts
new file mode 100644
index 00000000000..404d6eefd75
--- /dev/null
+++ b/src/node/hooks/express/adminsettings.ts
@@ -0,0 +1,58 @@
+'use strict';
+
+import {required} from '../../eejs';
+import {promises as fsp} from "fs";
+
+import {aCallAll} from "../../../static/js/pluginfw/hooks";
+
+import {update} from "../../../static/js/pluginfw/plugins";
+
+import {reloadSettings, settingsFilename, showSettingsInAdminPage} from "../../utils/Settings";
+import * as settings from "../../utils/Settings";
+
+export const expressCreateServer = (hookName, {app}) => {
+ app.get('/admin/settings', (req, res) => {
+ res.send(required('ep_etherpad-lite/templates/admin/settings.html', {
+ req,
+ settings: '',
+ errors: [],
+ }));
+ });
+};
+
+export const socketio = (hookName, {io}) => {
+ io.of('/settings').on('connection', (socket) => {
+ const {session: {user: {is_admin: isAdmin} = {}} = {}}:SessionSocketModel = socket.conn.request;
+ if (!isAdmin) return;
+
+ socket.on('load', async (query) => {
+ let data;
+ try {
+ data = await fsp.readFile(settingsFilename, 'utf8');
+ } catch (err) {
+ return console.log(err);
+ }
+ // if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
+ //FIXME Is this intentional to never change
+ // @ts-ignore
+ if (showSettingsInAdminPage === false) {
+ socket.emit('settings', {results: 'NOT_ALLOWED'});
+ } else {
+ socket.emit('settings', {results: data});
+ }
+ });
+
+ socket.on('saveSettings', async (newSettings) => {
+ await fsp.writeFile(settingsFilename, newSettings);
+ socket.emit('saveprogress', 'saved');
+ });
+
+ socket.on('restartServer', async () => {
+ console.log('Admin request to restart server through a socket on /admin/settings');
+ reloadSettings();
+ await update();
+ await aCallAll('loadSettings', {});
+ await aCallAll('restartServer');
+ });
+ });
+};
diff --git a/src/node/hooks/express/apicalls.js b/src/node/hooks/express/apicalls.ts
similarity index 80%
rename from src/node/hooks/express/apicalls.js
rename to src/node/hooks/express/apicalls.ts
index 010ab14e520..dc15d893849 100644
--- a/src/node/hooks/express/apicalls.js
+++ b/src/node/hooks/express/apicalls.ts
@@ -1,12 +1,15 @@
'use strict';
-const log4js = require('log4js');
-const clientLogger = log4js.getLogger('client');
-const {Formidable} = require('formidable');
-const apiHandler = require('../../handler/APIHandler');
-const util = require('util');
+import log4js from "log4js";
+
+import {Formidable} from "formidable";
+
+import {latestApiVersion} from "../../handler/APIHandler";
-exports.expressPreSession = async (hookName, {app}) => {
+import util from "util";
+
+const clientLogger = log4js.getLogger('client');
+export const expressPreSession = async (hookName, {app}) => {
// The Etherpad client side sends information about how a disconnect happened
app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
new Formidable().parse(req, (err, fields, files) => {
@@ -26,7 +29,7 @@ exports.expressPreSession = async (hookName, {app}) => {
// The Etherpad client side sends information about client side javscript errors
app.post('/jserror', (req, res, next) => {
(async () => {
- const data = JSON.parse(await parseJserrorForm(req));
+ const data = JSON.parse(await parseJserrorForm(req));
clientLogger.warn(`${data.msg} --`, {
[util.inspect.custom]: (depth, options) => {
// Depth is forced to infinity to ensure that all of the provided data is logged.
@@ -40,6 +43,6 @@ exports.expressPreSession = async (hookName, {app}) => {
// Provide a possibility to query the latest available API version
app.get('/api', (req, res) => {
- res.json({currentVersion: apiHandler.latestApiVersion});
- });
-};
+ res.json({currentVersion: latestApiVersion});
+ })
+}
diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.ts
similarity index 66%
rename from src/node/hooks/express/errorhandling.js
rename to src/node/hooks/express/errorhandling.ts
index 884ca9be0b6..9e2992251f7 100644
--- a/src/node/hooks/express/errorhandling.js
+++ b/src/node/hooks/express/errorhandling.ts
@@ -1,9 +1,10 @@
'use strict';
-const stats = require('../../stats');
+import {createCollection} from '../../stats';
-exports.expressCreateServer = (hook_name, args, cb) => {
- exports.app = args.app;
+export let app:any;
+export const expressCreateServer = (hook_name, args, cb) => {
+ app = args.app;
// Handle errors
args.app.use((err, req, res, next) => {
@@ -12,7 +13,7 @@ exports.expressCreateServer = (hook_name, args, cb) => {
// allowing you to respond however you like
res.status(500).send({error: 'Sorry, something bad happened!'});
console.error(err.stack ? err.stack : err.toString());
- stats.meter('http500').mark();
+ createCollection.meter('http500').mark();
});
return cb();
diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.ts
similarity index 63%
rename from src/node/hooks/express/importexport.js
rename to src/node/hooks/express/importexport.ts
index 96f0efab484..5d4101ead49 100644
--- a/src/node/hooks/express/importexport.js
+++ b/src/node/hooks/express/importexport.ts
@@ -1,23 +1,23 @@
'use strict';
-const hasPadAccess = require('../../padaccess');
-const settings = require('../../utils/Settings');
-const exportHandler = require('../../handler/ExportHandler');
-const importHandler = require('../../handler/ImportHandler');
-const padManager = require('../../db/PadManager');
-const readOnlyManager = require('../../db/ReadOnlyManager');
-const rateLimit = require('express-rate-limit');
-const securityManager = require('../../db/SecurityManager');
-const webaccess = require('./webaccess');
+import hasPadAccess from '../../padaccess';
+import {exportAvailable, importExportRateLimiting} from '../../utils/Settings';
+import {doExport} from '../../handler/ExportHandler';
+import {doImport2} from '../../handler/ImportHandler';
+import {doesPadExist} from '../../db/PadManager';
+import {getPadId, isReadOnlyId} from '../../db/ReadOnlyManager';
+import rateLimit from 'express-rate-limit';
+import {checkAccess} from '../../db/SecurityManager';
+import {userCanModify} from './webaccess';
-exports.expressCreateServer = (hookName, args, cb) => {
- settings.importExportRateLimiting.onLimitReached = (req, res, options) => {
+export const expressCreateServer = (hookName, args, cb) => {
+ importExportRateLimiting.onLimitReached = (req, res, options) => {
// when the rate limiter triggers, write a warning in the logs
console.warn('Import/Export rate limiter triggered on ' +
`"${req.originalUrl}" for IP address ${req.ip}`);
};
// The rate limiter is created in this hook so that restarting the server resets the limiter.
- const limiter = rateLimit(settings.importExportRateLimiting);
+ const limiter = rateLimit(importExportRateLimiting);
// handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter);
@@ -30,7 +30,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
}
// if abiword is disabled, and this is a format we only support with abiword, output a message
- if (settings.exportAvailable() === 'no' &&
+ if (exportAvailable() === 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured');
@@ -48,19 +48,19 @@ exports.expressCreateServer = (hookName, args, cb) => {
let padId = req.params.pad;
let readOnlyId = null;
- if (readOnlyManager.isReadOnlyId(padId)) {
+ if (isReadOnlyId(padId)) {
readOnlyId = padId;
- padId = await readOnlyManager.getPadId(readOnlyId);
+ padId = await getPadId(readOnlyId);
}
- const exists = await padManager.doesPadExists(padId);
+ const exists = await doesPadExist(padId);
if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next();
}
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
- await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
+ await doExport(req, res, padId, readOnlyId, req.params.type);
}
})().catch((err) => next(err || new Error(err)));
});
@@ -69,13 +69,13 @@ exports.expressCreateServer = (hookName, args, cb) => {
args.app.use('/p/:pad/import', limiter);
args.app.post('/p/:pad/import', (req, res, next) => {
(async () => {
- const {session: {user} = {}} = req;
- const {accessStatus, authorID: authorId} = await securityManager.checkAccess(
+ const {session: {user} = {}}:SessionSocketModel = req;
+ const {accessStatus, authorID: authorId} = await checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
- if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) {
+ if (accessStatus !== 'grant' || !userCanModify(req.params.pad, req)) {
return res.status(403).send('Forbidden');
}
- await importHandler.doImport(req, res, req.params.pad, authorId);
+ await doImport2(req, res, req.params.pad, authorId);
})().catch((err) => next(err || new Error(err)));
});
diff --git a/src/node/hooks/express/openapi.js b/src/node/hooks/express/openapi.ts
similarity index 98%
rename from src/node/hooks/express/openapi.js
rename to src/node/hooks/express/openapi.ts
index 0531854aa5b..68001bce7d4 100644
--- a/src/node/hooks/express/openapi.js
+++ b/src/node/hooks/express/openapi.ts
@@ -1,5 +1,8 @@
'use strict';
+import {ResponseSpec} from "../../models/ResponseSpec";
+import {APIResource} from "../../models/APIResource";
+
/**
* node/hooks/express/openapi.js
*
@@ -54,7 +57,7 @@ const APIPathStyle = {
};
// API resources - describe your API endpoints here
-const resources = {
+const resources: APIResource = {
// Group
group: {
create: {
@@ -375,7 +378,8 @@ const defaultResponses = {
const defaultResponseRefs = {
200: {
- $ref: '#/components/responses/Success',
+ $ref: '#/components/responses/Success', content: undefined
+
},
400: {
$ref: '#/components/responses/ApiError',
@@ -491,7 +495,11 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
// build operations
for (const funcName of Object.keys(apiHandler.version[version])) {
- let operation = {};
+ let operation: ResponseSpec = {
+ parameters: undefined,
+ _restPath: "",
+ operationId: undefined
+ };
if (operations[funcName]) {
operation = {...operations[funcName]};
} else {
@@ -540,7 +548,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
return definition;
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
// create openapi-backend handlers for each api version under /api/{version}/*
for (const version of Object.keys(apiHandler.version)) {
// we support two different styles of api: flat + rest
diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.ts
similarity index 78%
rename from src/node/hooks/express/padurlsanitize.js
rename to src/node/hooks/express/padurlsanitize.ts
index ff1afa47700..233f6f97c1a 100644
--- a/src/node/hooks/express/padurlsanitize.js
+++ b/src/node/hooks/express/padurlsanitize.ts
@@ -1,18 +1,18 @@
'use strict';
-const padManager = require('../../db/PadManager');
+import {isValidPadId, sanitizePadId} from '../../db/PadManager';
-exports.expressCreateServer = (hookName, args, cb) => {
+export const expressCreateServer = (hookName, args, cb) => {
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', (req, res, next, padId) => {
(async () => {
// ensure the padname is valid and the url doesn't end with a /
- if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
+ if (!isValidPadId(padId) || /\/$/.test(req.url)) {
res.status(404).send('Such a padname is forbidden');
return;
}
- const sanitizedPadId = await padManager.sanitizePadId(padId);
+ const sanitizedPadId = await sanitizePadId(padId);
if (sanitizedPadId === padId) {
// the pad id was fine, so just render it
diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.ts
similarity index 85%
rename from src/node/hooks/express/socketio.js
rename to src/node/hooks/express/socketio.ts
index edb6799405b..23c2623299d 100644
--- a/src/node/hooks/express/socketio.js
+++ b/src/node/hooks/express/socketio.ts
@@ -1,21 +1,21 @@
'use strict';
-const events = require('events');
-const express = require('../express');
-const log4js = require('log4js');
-const proxyaddr = require('proxy-addr');
-const settings = require('../../utils/Settings');
-const socketio = require('socket.io');
-const socketIORouter = require('../../handler/SocketIORouter');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const padMessageHandler = require('../../handler/PadMessageHandler');
+import events from 'events';
+import {sessionMiddleware} from '../express';
+import log4js from 'log4js';
+import proxyaddr from 'proxy-addr';
+import {socketIo, socketTransportProtocols, trustProxy} from '../../utils/Settings';
+import socketio from 'socket.io';
+import {addComponent, setSocketIO} from '../../handler/SocketIORouter';
+import {callAll} from '../../../static/js/pluginfw/hooks';
+import * as padMessageHandler from '../../handler/PadMessageHandler';
let io;
const logger = log4js.getLogger('socket.io');
const sockets = new Set();
const socketsEvents = new events.EventEmitter();
-exports.expressCloseServer = async () => {
+export const expressCloseServer = async () => {
if (io == null) return;
logger.info('Closing socket.io engine...');
// Close the socket.io engine to disconnect existing clients and reject new clients. Don't call
@@ -46,13 +46,13 @@ exports.expressCloseServer = async () => {
logger.info('All socket.io clients have disconnected');
};
-exports.expressCreateServer = (hookName, args, cb) => {
+export const expressCreateServer = (hookName, args, cb) => {
// init socket.io and redirect all requests to the MessageHandler
// there shouldn't be a browser that isn't compatible to all
// transports in this list at once
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
io = socketio({
- transports: settings.socketTransportProtocols,
+ transports: socketTransportProtocols,
}).listen(args.server, {
/*
* Do not set the "io" cookie.
@@ -74,7 +74,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
* https://github.com/socketio/socket.io/issues/2276#issuecomment-147184662 (not totally true, actually, see above)
*/
cookie: false,
- maxHttpBufferSize: settings.socketIo.maxHttpBufferSize,
+ maxHttpBufferSize: socketIo.maxHttpBufferSize,
});
io.on('connect', (socket) => {
@@ -90,7 +90,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
const req = socket.request;
// Express sets req.ip but socket.io does not. Replicate Express's behavior here.
if (req.ip == null) {
- if (settings.trustProxy) {
+ if (trustProxy) {
req.ip = proxyaddr(req, args.app.get('trust proxy fn'));
} else {
req.ip = socket.handshake.address;
@@ -102,7 +102,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
req.headers.cookie = socket.handshake.query.cookie;
}
// See: https://socket.io/docs/faq/#Usage-with-express-session
- express.sessionMiddleware(req, {}, next);
+ sessionMiddleware(req, {}, next);
});
io.use((socket, next) => {
@@ -130,10 +130,10 @@ exports.expressCreateServer = (hookName, args, cb) => {
// if(settings.minify) io.enable('browser client minification');
// Initialize the Socket.IO Router
- socketIORouter.setSocketIO(io);
- socketIORouter.addComponent('pad', padMessageHandler);
+ setSocketIO(io);
+ addComponent('pad', padMessageHandler);
- hooks.callAll('socketio', {app: args.app, io, server: args.server});
+ callAll('socketio', {app: args.app, io, server: args.server});
return cb();
};
diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.ts
similarity index 58%
rename from src/node/hooks/express/specialpages.js
rename to src/node/hooks/express/specialpages.ts
index 4f41d8cd02f..8a21beaa830 100644
--- a/src/node/hooks/express/specialpages.js
+++ b/src/node/hooks/express/specialpages.ts
@@ -1,41 +1,41 @@
'use strict';
-const path = require('path');
-const eejs = require('../../eejs');
-const fs = require('fs');
+import path from 'path';
+import {required} from '../../eejs';
+import fs from 'fs';
const fsp = fs.promises;
-const toolbar = require('../../utils/toolbar');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const settings = require('../../utils/Settings');
-const util = require('util');
-const webaccess = require('./webaccess');
+import {} from '../../utils/toolbar';
+import {callAll} from '../../../static/js/pluginfw/hooks';
+import {favicon, getEpVersion, maxAge, root, skinName} from '../../utils/Settings';
+import util from 'util';
+import {userCanModify} from './webaccess';
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
// This endpoint is intended to conform to:
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
app.get('/health', (req, res) => {
res.set('Content-Type', 'application/health+json');
res.json({
status: 'pass',
- releaseId: settings.getEpVersion(),
+ releaseId: getEpVersion(),
});
});
app.get('/stats', (req, res) => {
- res.json(require('../../stats').toJSON());
+ res.json(required('../../stats').toJSON());
});
app.get('/javascript', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
+ res.send(required('ep_etherpad-lite/templates/javascript.html', {req}));
});
app.get('/robots.txt', (req, res) => {
let filePath =
- path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
+ path.join(root, 'src', 'static', 'skins', skinName, 'robots.txt');
res.sendFile(filePath, (err) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) {
- filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
+ filePath = path.join(root, 'src', 'static', 'robots.txt');
res.sendFile(filePath);
}
});
@@ -44,9 +44,9 @@ exports.expressPreSession = async (hookName, {app}) => {
app.get('/favicon.ico', (req, res, next) => {
(async () => {
const fns = [
- ...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []),
- path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'),
- path.join(settings.root, 'src', 'static', 'favicon.ico'),
+ ...(favicon ? [path.resolve(root, favicon)] : []),
+ path.join(root, 'src', 'static', 'skins', skinName, 'favicon.ico'),
+ path.join(root, 'src', 'static', 'favicon.ico'),
];
for (const fn of fns) {
try {
@@ -54,7 +54,7 @@ exports.expressPreSession = async (hookName, {app}) => {
} catch (err) {
continue;
}
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
await util.promisify(res.sendFile.bind(res))(fn);
return;
}
@@ -63,25 +63,25 @@ exports.expressPreSession = async (hookName, {app}) => {
});
};
-exports.expressCreateServer = (hookName, args, cb) => {
+export const expressCreateServer = (hookName, args, cb) => {
// serve index.html under /
args.app.get('/', (req, res) => {
- res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
+ res.send(required('ep_etherpad-lite/templates/index.html', {req}));
});
// serve pad.html under /p
args.app.get('/p/:pad', (req, res, next) => {
// The below might break for pads being rewritten
- const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
+ const isReadOnly = !userCanModify(req.params.pad, req);
- hooks.callAll('padInitToolbar', {
+ callAll('padInitToolbar', {
toolbar,
isReadOnly,
});
// can be removed when require-kernel is dropped
res.header('Feature-Policy', 'sync-xhr \'self\'');
- res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
+ res.send(required('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
@@ -90,11 +90,11 @@ exports.expressCreateServer = (hookName, args, cb) => {
// serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', (req, res, next) => {
- hooks.callAll('padInitToolbar', {
+ callAll('padInitToolbar', {
toolbar,
});
- res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
+ res.send(required('ep_etherpad-lite/templates/timeslider.html', {
req,
toolbar,
}));
diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.ts
similarity index 86%
rename from src/node/hooks/express/static.js
rename to src/node/hooks/express/static.ts
index 26c18995ab4..962b845f5f0 100644
--- a/src/node/hooks/express/static.js
+++ b/src/node/hooks/express/static.ts
@@ -19,8 +19,9 @@ const getTar = async () => {
};
const tarJson = await fs.readFile(path.join(settings.root, 'src/node/utils/tar.json'), 'utf8');
const tar = {};
- for (const [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) {
- const files = relativeFiles.map(prefixLocalLibraryPath);
+ for (let [key, relativeFiles] of Object.entries(JSON.parse(tarJson))) {
+ const relativeFilesMapped = relativeFiles as string[];
+ const files = relativeFilesMapped.map(prefixLocalLibraryPath);
tar[prefixLocalLibraryPath(key)] = files
.concat(files.map((p) => p.replace(/\.js$/, '')))
.concat(files.map((p) => `${p.replace(/\.js$/, '')}/index.js`));
@@ -28,7 +29,7 @@ const getTar = async () => {
return tar;
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
// Cache both minified and static.
const assetCache = new CachingMiddleware();
app.all(/\/javascripts\/(.*)/, assetCache.handle.bind(assetCache));
@@ -62,8 +63,9 @@ exports.expressPreSession = async (hookName, {app}) => {
const clientParts = plugins.parts.filter((part) => part.client_hooks != null);
const clientPlugins = {};
for (const name of new Set(clientParts.map((part) => part.plugin))) {
- clientPlugins[name] = {...plugins.plugins[name]};
- delete clientPlugins[name].package;
+ const mappedName = name as any
+ clientPlugins[mappedName] = {...plugins.plugins[mappedName]};
+ delete clientPlugins[mappedName].package;
}
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.ts
similarity index 79%
rename from src/node/hooks/express/tests.js
rename to src/node/hooks/express/tests.ts
index 66b47d2af95..5ecfef43b57 100644
--- a/src/node/hooks/express/tests.js
+++ b/src/node/hooks/express/tests.ts
@@ -1,11 +1,14 @@
'use strict';
-const path = require('path');
-const fsp = require('fs').promises;
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const sanitizePathname = require('../../utils/sanitizePathname');
-const settings = require('../../utils/Settings');
+import path from 'path';
+import {promises as fsp} from "fs";
+import {plugins} from "../../../static/js/pluginfw/plugin_defs";
+
+import sanitizePathname from "../../utils/sanitizePathname";
+
+import {enableAdminUITests, root} from "../../utils/Settings";
+import {Presession} from "../../models/Presession";
// Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/'
// instead of path.sep to separate pathname components.
const findSpecs = async (specDir) => {
@@ -29,16 +32,17 @@ const findSpecs = async (specDir) => {
return specs;
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
app.get('/tests/frontend/frontendTestSpecs.json', (req, res, next) => {
(async () => {
const modules = [];
- await Promise.all(Object.entries(plugins.plugins).map(async ([plugin, def]) => {
- let {package: {path: pluginPath}} = def;
+ await Promise.all(Object.entries(plugins).map(async ([plugin, def]) => {
+ const mappedDef = def as Presession;
+ let {package: {path: pluginPath}} = mappedDef;
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
const specDir = `${plugin === 'ep_etherpad-lite' ? '' : 'static/'}tests/frontend/specs`;
for (const spec of await findSpecs(path.join(pluginPath, specDir))) {
- if (plugin === 'ep_etherpad-lite' && !settings.enableAdminUITests &&
+ if (plugin === 'ep_etherpad-lite' && !enableAdminUITests &&
spec.startsWith('admin')) continue;
modules.push(`${plugin}/${specDir}/${spec.replace(/\.js$/, '')}`);
}
@@ -57,7 +61,7 @@ exports.expressPreSession = async (hookName, {app}) => {
})().catch((err) => next(err || new Error(err)));
});
- const rootTestFolder = path.join(settings.root, 'src/tests/frontend/');
+ const rootTestFolder = path.join(root, 'src/tests/frontend/');
app.get('/tests/frontend/index.html', (req, res) => {
res.redirect(['./', ...req.url.split('?').slice(1)].join('?'));
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.ts
similarity index 81%
rename from src/node/hooks/express/webaccess.js
rename to src/node/hooks/express/webaccess.ts
index 81ed69b0748..a7ea8c573be 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.ts
@@ -1,23 +1,31 @@
'use strict';
-const assert = require('assert').strict;
-const log4js = require('log4js');
+import {strict as assert} from "assert";
+
+import log4js from "log4js";
+
+import {requireAuthentication, requireAuthorization, setUsers, users} from "../../utils/Settings";
+
+import {deprecationNotices} from "../../../static/js/pluginfw/hooks";
+
+import {getPadId, isReadOnlyId} from "../../db/ReadOnlyManager";
+import {UserIndexedModel} from "../../models/UserIndexedModel";
+
const httpLogger = log4js.getLogger('http');
-const settings = require('../../utils/Settings');
-const hooks = require('../../../static/js/pluginfw/hooks');
-const readOnlyManager = require('../../db/ReadOnlyManager');
-hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
+import {aCallFirst as hookCall} from "../../../static/js/pluginfw/hooks";
+
+deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
// Promisified wrapper around hooks.aCallFirst.
const aCallFirst = (hookName, context, pred = null) => new Promise((resolve, reject) => {
- hooks.aCallFirst(hookName, context, (err, r) => err != null ? reject(err) : resolve(r), pred);
+ hookCall(hookName, context, (err, r) => err != null ? reject(err) : resolve(r));
});
const aCallFirst0 =
async (hookName, context, pred = null) => (await aCallFirst(hookName, context, pred))[0];
-exports.normalizeAuthzLevel = (level) => {
+export const normalizeAuthzLevel = (level) => {
if (!level) return false;
switch (level) {
case true:
@@ -32,20 +40,20 @@ exports.normalizeAuthzLevel = (level) => {
return false;
};
-exports.userCanModify = (padId, req) => {
- if (readOnlyManager.isReadOnlyId(padId)) return false;
- if (!settings.requireAuthentication) return true;
- const {session: {user} = {}} = req;
+export const userCanModify = (padId, req) => {
+ if (isReadOnlyId(padId)) return false;
+ if (!requireAuthentication) return true;
+ const {session: {user} = {}}:SessionSocketModel = req;
if (!user || user.readOnly) return false;
assert(user.padAuthorizations); // This is populated even if !settings.requireAuthorization.
- const level = exports.normalizeAuthzLevel(user.padAuthorizations[padId]);
+ const level = normalizeAuthzLevel(user.padAuthorizations[padId]);
return level && level !== 'readOnly';
};
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
-exports.authnFailureDelayMs = 1000;
+export let authnFailureDelayMs = 1000;
-const checkAccess = async (req, res, next) => {
+export const checkAccess = async (req, res, next) => {
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
// ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -88,16 +96,16 @@ const checkAccess = async (req, res, next) => {
// authentication is checked and once after (if settings.requireAuthorization is true).
const authorize = async () => {
const grant = async (level) => {
- 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.
const encodedPadId = (req.path.match(/^\/p\/([^/]*)/) || [])[1];
if (encodedPadId == null) return true;
let padId = decodeURIComponent(encodedPadId);
- if (readOnlyManager.isReadOnlyId(padId)) {
+ if (isReadOnlyId(padId)) {
// pad is read-only, first get the real pad ID
- padId = await readOnlyManager.getPadId(padId);
+ padId = await getPadId(padId);
if (padId == null) return false;
}
// The user was granted access to a pad. Remember the authorization level in the user's
@@ -108,11 +116,11 @@ const checkAccess = async (req, res, next) => {
};
const isAuthenticated = req.session && req.session.user;
if (isAuthenticated && req.session.user.is_admin) return await grant('create');
- const requireAuthn = requireAdmin || settings.requireAuthentication;
+ const requireAuthn = requireAdmin || requireAuthentication;
if (!requireAuthn) return await grant('create');
if (!isAuthenticated) return await grant(false);
if (requireAdmin && !req.session.user.is_admin) return await grant(false);
- if (!settings.requireAuthorization) return await grant('create');
+ if (!requireAuthorization) return await grant('create');
return await grant(await aCallFirst0('authorize', {req, res, next, resource: req.path}));
};
@@ -131,8 +139,10 @@ const checkAccess = async (req, res, next) => {
// page).
// ///////////////////////////////////////////////////////////////////////////////////////////////
- if (settings.users == null) settings.users = {};
- const ctx = {req, res, users: settings.users, next};
+ if (users == null) setUsers({});
+ const ctx = {req, res, users: users, next, username: undefined,
+ password: undefined
+ };
// If the HTTP basic auth header is present, extract the username and password so it can be given
// to authn plugins.
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
@@ -148,7 +158,7 @@ const checkAccess = async (req, res, next) => {
}
if (!(await aCallFirst0('authenticate', ctx))) {
// Fall back to HTTP basic auth.
- const {[ctx.username]: {password} = {}} = settings.users;
+ const {[ctx.username]: {password} = {password: undefined}}:UserIndexedModel = users;
if (!httpBasicAuth || !ctx.username || password == null || password !== ctx.password) {
httpLogger.info(`Failed authentication from IP ${req.ip}`);
if (await aCallFirst0('authnFailure', {req, res})) return;
@@ -156,14 +166,14 @@ const checkAccess = async (req, res, next) => {
// No plugin handled the authentication failure. Fall back to basic authentication.
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;
}
- settings.users[ctx.username].username = ctx.username;
+ users[ctx.username].username = ctx.username;
// Make a shallow copy so that the password property can be deleted (to prevent it from
// appearing in logs or in the database) without breaking future authentication attempts.
- req.session.user = {...settings.users[ctx.username]};
+ req.session.user = {...users[ctx.username]};
delete req.session.user.password;
}
if (req.session.user == null) {
@@ -190,6 +200,12 @@ const checkAccess = async (req, res, next) => {
* Express middleware to authenticate the user and check authorization. Must be installed after the
* express-session middleware.
*/
-exports.checkAccess = (req, res, next) => {
+// FIXME why same method twice?
+export const checkAccess2 = (req, res, next) => {
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
};
+
+// Setters
+export const setauthnFailureDelayMs = (value) => {
+ authnFailureDelayMs = value;
+}
diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.ts
similarity index 73%
rename from src/node/hooks/i18n.js
rename to src/node/hooks/i18n.ts
index c54348867c8..001e87bd07d 100644
--- a/src/node/hooks/i18n.js
+++ b/src/node/hooks/i18n.ts
@@ -1,12 +1,13 @@
'use strict';
-const languages = require('languages4translatewiki');
-const fs = require('fs');
-const path = require('path');
-const _ = require('underscore');
-const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js');
-const existsSync = require('../utils/path_exists');
-const settings = require('../utils/Settings');
+import languages from 'languages4translatewiki';
+import fs from 'fs';
+import path from 'path';
+import _ from 'underscore';
+import {plugins} from '../../static/js/pluginfw/plugin_defs.js';
+import {check} from '../utils/path_exists';
+import {customLocaleStrings, maxAge, root} from '../utils/Settings';
+import {Presession} from "../models/Presession";
// returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...}
@@ -17,7 +18,7 @@ const getAllLocales = () => {
// into `locales2paths` (files from various dirs are grouped by lang code)
// (only json files with valid language code as name)
const extractLangs = (dir) => {
- if (!existsSync(dir)) return;
+ if (!check(dir)) return;
let stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
@@ -37,18 +38,19 @@ const getAllLocales = () => {
};
// add core supported languages first
- extractLangs(path.join(settings.root, 'src/locales'));
+ extractLangs(path.join(root, 'src/locales'));
// add plugins languages (if any)
- for (const {package: {path: pluginPath}} of Object.values(pluginDefs.plugins)) {
+ for (const val of Object.values(plugins)) {
+ const pluginPath:Presession = val as Presession
// plugin locales should overwrite etherpad's core locales
- if (pluginPath.endsWith('/ep_etherpad-lite') === true) continue;
- extractLangs(path.join(pluginPath, 'locales'));
+ if (pluginPath.package.path.endsWith('/ep_etherpad-lite') === true) continue;
+ extractLangs(path.join(pluginPath.package.path, 'locales'));
}
// Build a locale index (merge all locale data other than user-supplied overrides)
const locales = {};
- _.each(locales2paths, (files, langcode) => {
+ _.each(locales2paths, (files:[], langcode) => {
locales[langcode] = {};
files.forEach((file) => {
@@ -68,9 +70,9 @@ const getAllLocales = () => {
const wrongFormatErr = Error(
'customLocaleStrings in wrong format. See documentation ' +
'for Customization for Administrators, under Localization.');
- if (settings.customLocaleStrings) {
- if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
- _.each(settings.customLocaleStrings, (overrides, langcode) => {
+ if (customLocaleStrings) {
+ if (typeof customLocaleStrings !== 'object') throw wrongFormatErr;
+ _.each(customLocaleStrings, (overrides, langcode) => {
if (typeof overrides !== 'object') throw wrongFormatErr;
_.each(overrides, (localeString, key) => {
if (typeof localeString !== 'string') throw wrongFormatErr;
@@ -84,7 +86,7 @@ const getAllLocales = () => {
// returns a hash of all available languages availables with nativeName and direction
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
-const getAvailableLangs = (locales) => {
+export const getAvailableLangs = (locales) => {
const result = {};
for (const langcode of Object.keys(locales)) {
result[langcode] = languages.getLanguageInfo(langcode);
@@ -102,17 +104,17 @@ const generateLocaleIndex = (locales) => {
};
-exports.expressPreSession = async (hookName, {app}) => {
+export const expressPreSession = async (hookName, {app}) => {
// regenerate locales on server restart
const locales = getAllLocales();
const localeIndex = generateLocaleIndex(locales);
- exports.availableLangs = getAvailableLangs(locales);
+ let availableLangs = getAvailableLangs(locales);
app.get('/locales/:locale', (req, res) => {
// works with /locale/en and /locale/en.json requests
const locale = req.params.locale.split('.')[0];
- if (Object.prototype.hasOwnProperty.call(exports.availableLangs, locale)) {
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ if (Object.prototype.hasOwnProperty.call(availableLangs, locale)) {
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
} else {
@@ -121,7 +123,7 @@ exports.expressPreSession = async (hookName, {app}) => {
});
app.get('/locales.json', (req, res) => {
- res.setHeader('Cache-Control', `public, max-age=${settings.maxAge}`);
+ res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(localeIndex);
});
diff --git a/src/node/models/APIResource.ts b/src/node/models/APIResource.ts
new file mode 100644
index 00000000000..862e15aec5d
--- /dev/null
+++ b/src/node/models/APIResource.ts
@@ -0,0 +1,48 @@
+import {ResponseSpec} from "./ResponseSpec";
+
+export type APIResource = {
+ group:{
+ create:ResponseSpec,
+ createIfNotExistsFor: ResponseSpec,
+ delete: ResponseSpec,
+ listPads: ResponseSpec,
+ createPad: ResponseSpec,
+ listSessions: ResponseSpec,
+ list: ResponseSpec,
+ },
+ author: {
+ create: ResponseSpec,
+ createIfNotExistsFor: ResponseSpec,
+ listPads: ResponseSpec,
+ listSessions: ResponseSpec,
+ getName: ResponseSpec,
+ },
+ session:{
+ create: ResponseSpec,
+ delete: ResponseSpec,
+ info: ResponseSpec,
+ },
+ pad:{
+ listAll: ResponseSpec,
+ createDiffHTML: ResponseSpec,
+ create: ResponseSpec,
+ getText: ResponseSpec,
+ setText: ResponseSpec,
+ getHTML: ResponseSpec,
+ setHTML: ResponseSpec,
+ getRevisionsCount: ResponseSpec,
+ getLastEdited: ResponseSpec,
+ delete: ResponseSpec,
+ getReadOnlyID: ResponseSpec,
+ setPublicStatus: ResponseSpec,
+ getPublicStatus: ResponseSpec,
+ authors: ResponseSpec,
+ usersCount: ResponseSpec,
+ users: ResponseSpec,
+ sendClientsMessage: ResponseSpec,
+ checkToken: ResponseSpec,
+ getChatHistory: ResponseSpec,
+ getChatHead: ResponseSpec,
+ appendChatMessage: ResponseSpec,
+ }
+}
diff --git a/src/node/models/CMDArgv.ts b/src/node/models/CMDArgv.ts
new file mode 100644
index 00000000000..9e663cfa751
--- /dev/null
+++ b/src/node/models/CMDArgv.ts
@@ -0,0 +1,3 @@
+export type CMDArgv = {
+ [key: string]: any
+}
diff --git a/src/node/models/CMDOptions.ts b/src/node/models/CMDOptions.ts
new file mode 100644
index 00000000000..925170e1d32
--- /dev/null
+++ b/src/node/models/CMDOptions.ts
@@ -0,0 +1,11 @@
+export type CMDOptions = {
+ cwd?: string,
+ stdio?: string|any[],
+ env?: NodeJS.ProcessEnv
+}
+
+export type CMDPromise = {
+ stdout: string,
+ stderr: string,
+ child: any
+}
diff --git a/src/node/models/ErrorCaused.ts b/src/node/models/ErrorCaused.ts
new file mode 100644
index 00000000000..1b821bc9d68
--- /dev/null
+++ b/src/node/models/ErrorCaused.ts
@@ -0,0 +1,14 @@
+import ts from "typescript/lib/tsserverlibrary";
+
+export class ErrorCaused extends Error{
+ cause: Error;
+ constructor(message: string, cause: Error) {
+ super(message);
+ this.cause = cause;
+ this.name = "ErrorCaused";
+ }
+}
+
+type ErrorCause = {
+
+}
diff --git a/src/node/models/LogLevel.ts b/src/node/models/LogLevel.ts
new file mode 100644
index 00000000000..bd007c23f22
--- /dev/null
+++ b/src/node/models/LogLevel.ts
@@ -0,0 +1 @@
+export type LogLevel = "DEBUG"|"INFO"|"WARN"|"ERROR"
diff --git a/src/node/models/PadDiffModel.ts b/src/node/models/PadDiffModel.ts
new file mode 100644
index 00000000000..c201b1fb4dc
--- /dev/null
+++ b/src/node/models/PadDiffModel.ts
@@ -0,0 +1,3 @@
+export type PadDiffModel = {
+ _authors: any[]
+}
diff --git a/src/node/models/Plugin.ts b/src/node/models/Plugin.ts
new file mode 100644
index 00000000000..08abf0d6918
--- /dev/null
+++ b/src/node/models/Plugin.ts
@@ -0,0 +1,6 @@
+export type Plugin = {
+ package: {
+ name: string,
+ version: string
+ }
+}
diff --git a/src/node/models/Presession.ts b/src/node/models/Presession.ts
new file mode 100644
index 00000000000..bd8146bc340
--- /dev/null
+++ b/src/node/models/Presession.ts
@@ -0,0 +1,5 @@
+export type Presession = {
+ package:{
+ path: string,
+ }
+}
diff --git a/src/node/models/ResponseSpec.ts b/src/node/models/ResponseSpec.ts
new file mode 100644
index 00000000000..db32008af14
--- /dev/null
+++ b/src/node/models/ResponseSpec.ts
@@ -0,0 +1,43 @@
+export type ResponseSpec = {
+ parameters?: ParameterSpec[],
+ _restPath?: string,
+ operationId?: string,
+ responses?: any
+ description?: string,
+ summary?: string,
+ responseSchema?:{
+ groupID?: APIResponseSpecType
+ groupIDs?: APIResponseSpecType
+ padIDs?: APIResponseSpecType,
+ sessions?: APIResponseSpecType,
+ authorID?: APIResponseSpecType,
+ info?: APIResponseSpecType,
+ sessionID?: APIResponseSpecType,
+ text?: APIResponseSpecType,
+ html?: APIResponseSpecType,
+ revisions?: APIResponseSpecType,
+ lastEdited?: APIResponseSpecType,
+ readOnlyID?: APIResponseSpecType,
+ publicStatus?: APIResponseSpecType,
+ authorIDs?: APIResponseSpecType,
+ padUsersCount?: APIResponseSpecType,
+ padUsers?: APIResponseSpecType,
+ messages?: APIResponseSpecType,
+ chatHead?: APIResponseSpecType,
+ }
+}
+
+
+export type APIResponseSpecType = {
+ type?:string,
+ items?: {
+ type?:string,
+ $ref?:string
+ },
+ $ref?:string
+}
+
+
+export type ParameterSpec = {
+ $ref?: string,
+}
diff --git a/src/node/models/Revision.ts b/src/node/models/Revision.ts
new file mode 100644
index 00000000000..a57be4e0fcb
--- /dev/null
+++ b/src/node/models/Revision.ts
@@ -0,0 +1,7 @@
+export type Revision = {
+ revNum: number,
+ savedById: string,
+ label: string,
+ timestamp: number,
+ id: string,
+}
diff --git a/src/node/models/SessionInfo.ts b/src/node/models/SessionInfo.ts
new file mode 100644
index 00000000000..520b21a2678
--- /dev/null
+++ b/src/node/models/SessionInfo.ts
@@ -0,0 +1,10 @@
+export type SessionInfo = {
+ [key: string]:{
+ time: any;
+ rev: number;
+ readOnlyPadId: any;
+ auth: { padID: any; sessionID: any; token: any };
+ readonly: boolean;
+ padId: string,
+ author: string
+ }}
diff --git a/src/node/models/SessionModel.ts b/src/node/models/SessionModel.ts
new file mode 100644
index 00000000000..2b2d5e80acb
--- /dev/null
+++ b/src/node/models/SessionModel.ts
@@ -0,0 +1,5 @@
+export type SessionModel = {
+ cookie: {
+ expires?: string
+ }
+}
diff --git a/src/node/models/SessionSocketModel.ts b/src/node/models/SessionSocketModel.ts
new file mode 100644
index 00000000000..ff0c730aa0f
--- /dev/null
+++ b/src/node/models/SessionSocketModel.ts
@@ -0,0 +1,10 @@
+type SessionSocketModel = {
+ session:{
+ user?: {
+ username?: string,
+ is_admin?: boolean
+ readOnly?: boolean,
+ padAuthorizations?: any
+ }
+ }
+}
diff --git a/src/node/models/UserIndexedModel.ts b/src/node/models/UserIndexedModel.ts
new file mode 100644
index 00000000000..0c808a41b69
--- /dev/null
+++ b/src/node/models/UserIndexedModel.ts
@@ -0,0 +1,5 @@
+export type UserIndexedModel = {
+ [key: string]: {
+ password?: string|undefined,
+ }
+}
diff --git a/src/node/padaccess.js b/src/node/padaccess.ts
similarity index 62%
rename from src/node/padaccess.js
rename to src/node/padaccess.ts
index e9cc7cde5ec..a18bd83eda3 100644
--- a/src/node/padaccess.js
+++ b/src/node/padaccess.ts
@@ -1,10 +1,10 @@
'use strict';
-const securityManager = require('./db/SecurityManager');
+import {checkAccess} from './db/SecurityManager';
// checks for padAccess
-module.exports = async (req, res) => {
- const {session: {user} = {}} = req;
- const accessObj = await securityManager.checkAccess(
+export default async (req, res) => {
+ const {session: {user} = {}}:SessionSocketModel = req;
+ const accessObj = await checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
if (accessObj.accessStatus === 'grant') {
diff --git a/src/node/server.js b/src/node/server.ts
old mode 100755
new mode 100644
similarity index 79%
rename from src/node/server.js
rename to src/node/server.ts
index 6a494fe1730..3546e458423
--- a/src/node/server.js
+++ b/src/node/server.ts
@@ -24,10 +24,22 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-log4js.replaceConsole();
-
-const settings = require('./utils/Settings');
+import log4js from 'log4js';
+import * as settings from "./utils/Settings";
+/*
+ * early check for version compatibility before calling
+ * any modules that require newer versions of NodeJS
+ */
+import {checkDeprecationStatus, enforceMinNodeVersion} from './utils/NodeVersion'
+import {Gate} from './utils/promises';
+import * as UpdateCheck from "./utils/UpdateCheck";
+import {Plugin} from "./models/Plugin";
+import {db} from './db/DB'
+import {createServer, server} from './hooks/express';
+import {aCallAll} from '../static/js/pluginfw/hooks'
+import * as pluginDefs from '../static/js/pluginfw/plugin_defs'
+import * as plugins from "../static/js/pluginfw/plugins";
+import {createCollection} from "./stats";
let wtfnode;
if (settings.dumpOnUncleanExit) {
@@ -36,24 +48,11 @@ if (settings.dumpOnUncleanExit) {
wtfnode = require('wtfnode');
}
-/*
- * early check for version compatibility before calling
- * any modules that require newer versions of NodeJS
- */
-const NodeVersion = require('./utils/NodeVersion');
-NodeVersion.enforceMinNodeVersion('12.17.0');
-NodeVersion.checkDeprecationStatus('12.17.0', '1.9.0');
-
-const UpdateCheck = require('./utils/UpdateCheck');
-const db = require('./db/DB');
-const express = require('./hooks/express');
-const hooks = require('../static/js/pluginfw/hooks');
-const pluginDefs = require('../static/js/pluginfw/plugin_defs');
-const plugins = require('../static/js/pluginfw/plugins');
-const {Gate} = require('./utils/promises');
-const stats = require('./stats');
+enforceMinNodeVersion('12.17.0');
+checkDeprecationStatus('12.17.0', '1.9.0');
const logger = log4js.getLogger('server');
+console.log = logger.info.bind(logger); // do the same for others - console.debug, etc.
const State = {
INITIAL: 1,
@@ -70,22 +69,22 @@ let state = State.INITIAL;
const removeSignalListener = (signal, listener) => {
logger.debug(`Removing ${signal} listener because it might interfere with shutdown tasks. ` +
- `Function code:\n${listener.toString()}\n` +
- `Current stack:\n${(new Error()).stack.split('\n').slice(1).join('\n')}`);
+ `Function code:\n${listener.toString()}\n` +
+ `Current stack:\n${(new Error()).stack.split('\n').slice(1).join('\n')}`);
process.off(signal, listener);
};
let startDoneGate;
-exports.start = async () => {
+export const start = async () => {
switch (state) {
case State.INITIAL:
break;
case State.STARTING:
await startDoneGate;
// Retry. Don't fall through because it might have transitioned to STATE_TRANSITION_FAILED.
- return await exports.start();
+ return await start();
case State.RUNNING:
- return express.server;
+ return server;
case State.STOPPING:
case State.STOPPED:
case State.EXITING:
@@ -100,16 +99,16 @@ exports.start = async () => {
state = State.STARTING;
try {
// Check if Etherpad version is up-to-date
- UpdateCheck.check();
+ UpdateCheck.default.check();
- stats.gauge('memoryUsage', () => process.memoryUsage().rss);
- stats.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed);
+ createCollection.gauge('memoryUsage', () => process.memoryUsage().rss);
+ createCollection.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed);
process.on('uncaughtException', (err) => {
logger.debug(`uncaught exception: ${err.stack || err}`);
// eslint-disable-next-line promise/no-promise-in-callback
- exports.exit(err)
+ exit(err)
.catch((err) => {
logger.error('Error in process exit', err);
// eslint-disable-next-line n/no-process-exit
@@ -118,7 +117,8 @@ exports.start = async () => {
});
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
- process.on('unhandledRejection', (err) => {
+ process.on('unhandledRejection', (err:Error) => {
+
logger.debug(`unhandled rejection: ${err.stack || err}`);
throw err;
});
@@ -128,10 +128,10 @@ exports.start = async () => {
// done cleaning up. See https://github.com/andywer/threads.js/pull/329 for an example of a
// problematic listener. This means that exports.exit is solely responsible for performing all
// necessary cleanup tasks.
- for (const listener of process.listeners(signal)) {
+ for (const listener of process.listeners(signal as any)) {
removeSignalListener(signal, listener);
}
- process.on(signal, exports.exit);
+ process.on(signal, exit);
// Prevent signal listeners from being added in the future.
process.on('newListener', (event, listener) => {
if (event !== signal) return;
@@ -141,37 +141,36 @@ exports.start = async () => {
await db.init();
await plugins.update();
- const installedPlugins = Object.values(pluginDefs.plugins)
+ const installedPlugins = (Object.values(pluginDefs.plugins) as Plugin[])
.filter((plugin) => plugin.package.name !== 'ep_etherpad-lite')
.map((plugin) => `${plugin.package.name}@${plugin.package.version}`)
.join(', ');
logger.info(`Installed plugins: ${installedPlugins}`);
logger.debug(`Installed parts:\n${plugins.formatParts()}`);
logger.debug(`Installed server-side hooks:\n${plugins.formatHooks('hooks', false)}`);
- await hooks.aCallAll('loadSettings', {settings});
- await hooks.aCallAll('createServer');
+ await aCallAll('loadSettings', {settings});
+ await aCallAll(createServer());
} catch (err) {
logger.error('Error occurred while starting Etherpad');
state = State.STATE_TRANSITION_FAILED;
startDoneGate.resolve();
- return await exports.exit(err);
+ return await exit(err);
}
logger.info('Etherpad is running');
state = State.RUNNING;
startDoneGate.resolve();
-
// Return the HTTP server to make it easier to write tests.
- return express.server;
+ return server;
};
const stopDoneGate = new Gate();
-exports.stop = async () => {
+export const stop = async () => {
switch (state) {
case State.STARTING:
- await exports.start();
+ await start();
// Don't fall through to State.RUNNING in case another caller is also waiting for startup.
- return await exports.stop();
+ return await stop();
case State.RUNNING:
break;
case State.STOPPING:
@@ -191,7 +190,7 @@ exports.stop = async () => {
try {
let timeout = null;
await Promise.race([
- hooks.aCallAll('shutdown'),
+ aCallAll('shutdown'),
new Promise((resolve, reject) => {
timeout = setTimeout(() => reject(new Error('Timed out waiting for shutdown tasks')), 3000);
}),
@@ -200,24 +199,26 @@ exports.stop = async () => {
} catch (err) {
logger.error('Error occurred while stopping Etherpad');
state = State.STATE_TRANSITION_FAILED;
+ // @ts-ignore
stopDoneGate.resolve();
- return await exports.exit(err);
+ return await exit(err);
}
logger.info('Etherpad stopped');
state = State.STOPPED;
+ // @ts-ignore
stopDoneGate.resolve();
};
let exitGate;
let exitCalled = false;
-exports.exit = async (err = null) => {
+export const exit = async (err = null) => {
/* eslint-disable no-process-exit */
if (err === 'SIGTERM') {
// Termination from SIGTERM is not treated as an abnormal termination.
logger.info('Received SIGTERM signal');
err = null;
} else if (err != null) {
- logger.error(`Metrics at time of fatal error:\n${JSON.stringify(stats.toJSON(), null, 2)}`);
+ logger.error(`Metrics at time of fatal error:\n${JSON.stringify(createCollection.toJSON(), null, 2)}`);
logger.error(err.stack || err.toString());
process.exitCode = 1;
if (exitCalled) {
@@ -231,11 +232,11 @@ exports.exit = async (err = null) => {
case State.STARTING:
case State.RUNNING:
case State.STOPPING:
- await exports.stop();
+ await stop();
// Don't fall through to State.STOPPED in case another caller is also waiting for stop().
// Don't pass err to exports.exit() because this err has already been processed. (If err is
// passed again to exit() then exit() will think that a second error occurred while exiting.)
- return await exports.exit();
+ return await exit();
case State.INITIAL:
case State.STOPPED:
case State.STATE_TRANSITION_FAILED:
@@ -257,13 +258,13 @@ exports.exit = async (err = null) => {
// on the timeout so that the timeout itself does not prevent Node.js from exiting.
setTimeout(() => {
logger.error('Something that should have been cleaned up during the shutdown hook (such as ' +
- 'a timer, worker thread, or open connection) is preventing Node.js from exiting');
+ 'a timer, worker thread, or open connection) is preventing Node.js from exiting');
if (settings.dumpOnUncleanExit) {
wtfnode.dump();
} else {
logger.error('Enable `dumpOnUncleanExit` setting to get a dump of objects preventing a ' +
- 'clean exit');
+ 'clean exit');
}
logger.error('Forcing an unclean exit...');
@@ -272,7 +273,7 @@ exports.exit = async (err = null) => {
logger.info('Waiting for Node.js to exit...');
state = State.WAITING_FOR_EXIT;
- /* eslint-enable no-process-exit */
};
-if (require.main === module) exports.start();
+start()
+ .then(c=>logger.info("Server started"));
diff --git a/src/node/stats.js b/src/node/stats.js
deleted file mode 100644
index cecaca20d16..00000000000
--- a/src/node/stats.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-const measured = require('measured-core');
-
-module.exports = measured.createCollection();
-
-module.exports.shutdown = async (hookName, context) => {
- module.exports.end();
-};
diff --git a/src/node/stats.ts b/src/node/stats.ts
new file mode 100644
index 00000000000..9d35395d55f
--- /dev/null
+++ b/src/node/stats.ts
@@ -0,0 +1,9 @@
+'use strict';
+
+import measured from 'measured-core'
+
+export const createCollection = measured.createCollection();
+
+export const shutdown = async (hookName, context) => {
+ module.exports.end();
+}
diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.ts
similarity index 77%
rename from src/node/utils/Abiword.js
rename to src/node/utils/Abiword.ts
index 1ed487ae14a..90cd88bdd53 100644
--- a/src/node/utils/Abiword.js
+++ b/src/node/utils/Abiword.ts
@@ -19,20 +19,23 @@
* limitations under the License.
*/
-const spawn = require('child_process').spawn;
-const async = require('async');
-const settings = require('./Settings');
-const os = require('os');
+import {spawn} from "child_process";
+
+import async from 'async';
+import {abiword} from './Settings';
+import os from 'os';
+
+export let convertFile;
// on windows we have to spawn a process for each convertion,
// cause the plugin abicommand doesn't exist on this platform
if (os.type().indexOf('Windows') > -1) {
- exports.convertFile = async (srcFile, destFile, type) => {
- const abiword = spawn(settings.abiword, [`--to=${destFile}`, srcFile]);
+ convertFile = async (srcFile, destFile, type) => {
+ const abiword2 = spawn(abiword, [`--to=${destFile}`, srcFile]);
let stdoutBuffer = '';
- abiword.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
- abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
- await new Promise((resolve, reject) => {
+ abiword2.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
+ abiword2.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
+ await new Promise((resolve, reject) => {
abiword.on('exit', (code) => {
if (code !== 0) return reject(new Error(`Abiword died with exit code ${code}`));
if (stdoutBuffer !== '') {
@@ -46,14 +49,14 @@ if (os.type().indexOf('Windows') > -1) {
// communicate with it via stdin/stdout
// thats much faster, about factor 10
} else {
- let abiword;
+ let abiword2;
let stdoutCallback = null;
const spawnAbiword = () => {
- abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
+ abiword2 = spawn(abiword, ['--plugin', 'AbiCommand']);
let stdoutBuffer = '';
let firstPrompt = true;
- abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
- abiword.on('exit', (code) => {
+ abiword2.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
+ abiword2.on('exit', (code) => {
spawnAbiword();
if (stdoutCallback != null) {
stdoutCallback(new Error(`Abiword died with exit code ${code}`));
@@ -84,7 +87,7 @@ if (os.type().indexOf('Windows') > -1) {
};
}, 1);
- exports.convertFile = async (srcFile, destFile, type) => {
+ convertFile = async (srcFile, destFile, type) => {
await queue.pushAsync({srcFile, destFile, type});
};
}
diff --git a/src/node/utils/AbsolutePaths.js b/src/node/utils/AbsolutePaths.ts
similarity index 93%
rename from src/node/utils/AbsolutePaths.js
rename to src/node/utils/AbsolutePaths.ts
index 73a96bb676d..e5514c6699e 100644
--- a/src/node/utils/AbsolutePaths.js
+++ b/src/node/utils/AbsolutePaths.ts
@@ -19,9 +19,10 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-const path = require('path');
-const _ = require('underscore');
+import log4js from 'log4js';
+import path from "path";
+
+import _ from "underscore";
const absPathLogger = log4js.getLogger('AbsolutePaths');
@@ -75,7 +76,7 @@ const popIfEndsWith = (stringArray, lastDesiredElements) => {
* @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application.
*/
-exports.findEtherpadRoot = () => {
+export const findEtherpadRoot = () => {
if (etherpadRoot != null) {
return etherpadRoot;
}
@@ -90,7 +91,7 @@ exports.findEtherpadRoot = () => {
*
* \src
*/
- let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
+ let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['dist']);
if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) {
/*
@@ -131,12 +132,12 @@ exports.findEtherpadRoot = () => {
* it is returned unchanged. Otherwise it is interpreted
* relative to exports.root.
*/
-exports.makeAbsolute = (somePath) => {
+export const makeAbsolute = (somePath) => {
if (path.isAbsolute(somePath)) {
return somePath;
}
- const rewrittenPath = path.join(exports.findEtherpadRoot(), somePath);
+ const rewrittenPath = path.join(findEtherpadRoot(), somePath);
absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`);
return rewrittenPath;
@@ -150,7 +151,7 @@ exports.makeAbsolute = (somePath) => {
* a subdirectory of the base one
* @return {boolean}
*/
-exports.isSubdir = (parent, arbitraryDir) => {
+export const isSubdir = (parent, arbitraryDir) => {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir);
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
diff --git a/src/node/utils/Cli.js b/src/node/utils/Cli.ts
similarity index 79%
rename from src/node/utils/Cli.js
rename to src/node/utils/Cli.ts
index a5cdee83af5..260478cce10 100644
--- a/src/node/utils/Cli.js
+++ b/src/node/utils/Cli.ts
@@ -21,33 +21,35 @@
*/
// An object containing the parsed command-line options
-exports.argv = {};
+import {CMDArgv} from "../models/CMDArgv";
-const argv = process.argv.slice(2);
-let arg, prevArg;
+const argvcmd = process.argv.slice(2);
+let arg, prevArg
+
+export const argv:CMDArgv|undefined = {};
// Loop through args
-for (let i = 0; i < argv.length; i++) {
+for (let i = 0; i < argvcmd.length; i++) {
arg = argv[i];
// Override location of settings.json file
if (prevArg === '--settings' || prevArg === '-s') {
- exports.argv.settings = arg;
+ argv.settings = arg;
}
// Override location of credentials.json file
if (prevArg === '--credentials') {
- exports.argv.credentials = arg;
+ argv.credentials = arg;
}
// Override location of settings.json file
if (prevArg === '--sessionkey') {
- exports.argv.sessionkey = arg;
+ argv.sessionkey = arg;
}
// Override location of settings.json file
if (prevArg === '--apikey') {
- exports.argv.apikey = arg;
+ argv.apikey = arg;
}
prevArg = arg;
diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.ts
similarity index 81%
rename from src/node/utils/ExportEtherpad.js
rename to src/node/utils/ExportEtherpad.ts
index e20739ad3ef..c20741840bd 100644
--- a/src/node/utils/ExportEtherpad.js
+++ b/src/node/utils/ExportEtherpad.ts
@@ -14,18 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {Stream} from "./Stream";
-const Stream = require('./Stream');
-const assert = require('assert').strict;
-const authorManager = require('../db/AuthorManager');
-const hooks = require('../../static/js/pluginfw/hooks');
-const padManager = require('../db/PadManager');
+import {strict as assert} from "assert";
-exports.getPadRaw = async (padId, readOnlyId) => {
+import {getAuthor} from "../db/AuthorManager";
+
+import {aCallAll} from "../../static/js/pluginfw/hooks";
+
+import {getPad} from "../db/PadManager";
+
+export const getPadRaw = async (padId, readOnlyId) => {
const dstPfx = `pad:${readOnlyId || padId}`;
const [pad, customPrefixes] = await Promise.all([
- padManager.getPad(padId),
- hooks.aCallAll('exportEtherpadAdditionalContent'),
+ getPad(padId),
+ aCallAll('exportEtherpadAdditionalContent'),
]);
const pluginRecords = await Promise.all(customPrefixes.map(async (customPrefix) => {
const srcPfx = `${customPrefix}:${padId}`;
@@ -43,7 +46,7 @@ exports.getPadRaw = async (padId, readOnlyId) => {
const records = (function* () {
for (const authorId of pad.getAllAuthors()) {
yield [`globalAuthor:${authorId}`, (async () => {
- const authorEntry = await authorManager.getAuthor(authorId);
+ const authorEntry = await getAuthor(authorId);
if (!authorEntry) return undefined; // Becomes unset when converted to JSON.
if (authorEntry.padIDs) authorEntry.padIDs = readOnlyId || padId;
return authorEntry;
@@ -55,7 +58,7 @@ exports.getPadRaw = async (padId, readOnlyId) => {
})();
const data = {[dstPfx]: pad};
for (const [dstKey, p] of new Stream(records).batch(100).buffer(99)) data[dstKey] = await p;
- await hooks.aCallAll('exportEtherpad', {
+ await aCallAll('exportEtherpad', {
pad,
data,
dstPadId: readOnlyId || padId,
diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.ts
similarity index 77%
rename from src/node/utils/ExportHelper.js
rename to src/node/utils/ExportHelper.ts
index 7962476e8a1..814ceef335d 100644
--- a/src/node/utils/ExportHelper.js
+++ b/src/node/utils/ExportHelper.ts
@@ -19,14 +19,13 @@
* limitations under the License.
*/
-const AttributeMap = require('../../static/js/AttributeMap');
-const Changeset = require('../../static/js/Changeset');
+import AttributeMap from '../../static/js/AttributeMap';
+import {deserializeOps, splitAttributionLines, subattribution} from '../../static/js/Changeset';
-exports.getPadPlainText = (pad, revNum) => {
- const _analyzeLine = exports._analyzeLine;
+export const getPadPlainText = (pad, revNum) => {
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n');
- const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
+ const attribLines = splitAttributionLines(atext.attribs, atext.text);
const apool = pad.pool;
const pieces = [];
@@ -45,14 +44,20 @@ exports.getPadPlainText = (pad, revNum) => {
};
-exports._analyzeLine = (text, aline, apool) => {
- const line = {};
+export const _analyzeLine = (text, aline, apool) => {
+ const line = {
+ listLevel: undefined,
+ text: undefined,
+ listTypeName: undefined,
+ aline: undefined,
+ start: undefined
+ };
// identify list
let lineMarker = 0;
line.listLevel = 0;
if (aline) {
- const [op] = Changeset.deserializeOps(aline);
+ const [op] = deserializeOps(aline);
if (op != null) {
const attribs = AttributeMap.fromString(op.attribs, apool);
let listType = attribs.get('list');
@@ -72,7 +77,7 @@ exports._analyzeLine = (text, aline, apool) => {
}
if (lineMarker) {
line.text = text.substring(1);
- line.aline = Changeset.subattribution(aline, 1);
+ line.aline = subattribution(aline, 1);
} else {
line.text = text;
line.aline = aline;
@@ -81,5 +86,5 @@ exports._analyzeLine = (text, aline, apool) => {
};
-exports._encodeWhitespace =
+export const _encodeWhitespace =
(s) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `${c.codePointAt(0)};`);
diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.ts
similarity index 89%
rename from src/node/utils/ExportHtml.js
rename to src/node/utils/ExportHtml.ts
index d14f40e6e7b..5de740f5516 100644
--- a/src/node/utils/ExportHtml.js
+++ b/src/node/utils/ExportHtml.ts
@@ -15,16 +15,27 @@
* limitations under the License.
*/
-const Changeset = require('../../static/js/Changeset');
-const attributes = require('../../static/js/attributes');
-const padManager = require('../db/PadManager');
-const _ = require('underscore');
-const Security = require('../../static/js/security');
-const hooks = require('../../static/js/pluginfw/hooks');
-const eejs = require('../eejs');
-const _analyzeLine = require('./ExportHelper')._analyzeLine;
-const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
-const padutils = require('../../static/js/pad_utils').padutils;
+import {
+ deserializeOps,
+ splitAttributionLines,
+ stringAssembler,
+ stringIterator,
+ subattribution
+} from '../../static/js/Changeset';
+import {decodeAttribString} from "../../static/js/attributes";
+
+import {getPad} from "../db/PadManager";
+
+import _ from "underscore";
+
+// FIXME this is a hack to get around the fact that we don't have a good way
+// @ts-ignore
+import {escapeHTML,escapeHTMLAttribute} from '../../static/js/security';
+import {aCallAll} from '../../static/js/pluginfw/hooks';
+import {required} from '../eejs';
+import {_analyzeLine, _encodeWhitespace} from "./ExportHelper";
+
+import {padutils} from "../../static/js/pad_utils";
const getPadHTML = async (pad, revNum) => {
let atext = pad.atext;
@@ -38,17 +49,17 @@ const getPadHTML = async (pad, revNum) => {
return await getHTMLFromAtext(pad, atext);
};
-const getHTMLFromAtext = async (pad, atext, authorColors) => {
+export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
- const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
+ const attribLines = splitAttributionLines(atext.attribs, atext.text);
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
await Promise.all([
// prepare tags stored as ['tag', true] to be exported
- hooks.aCallAll('exportHtmlAdditionalTags', pad).then((newProps) => {
+ aCallAll('exportHtmlAdditionalTags', pad).then((newProps) => {
newProps.forEach((prop) => {
tags.push(prop);
props.push(prop);
@@ -56,7 +67,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
}),
// prepare tags stored as ['tag', 'value'] to be exported. This will generate HTML with tags
// like
- hooks.aCallAll('exportHtmlAdditionalTagsWithData', pad).then((newProps) => {
+ aCallAll('exportHtmlAdditionalTagsWithData', pad).then((newProps) => {
newProps.forEach((prop) => {
tags.push(`span data-${prop[0]}="${prop[1]}"`);
props.push(prop);
@@ -124,8 +135,8 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
// Just bold Bold and italics Just italics
// becomes
// Just bold Bold and italics Just italics
- const taker = Changeset.stringIterator(text);
- const assem = Changeset.stringAssembler();
+ const taker = stringIterator(text);
+ const assem = stringAssembler();
const openTags = [];
const getSpanClassFor = (i) => {
@@ -197,7 +208,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
return;
}
- const ops = Changeset.deserializeOps(Changeset.subattribution(attribs, idx, idx + numChars));
+ const ops = deserializeOps(subattribution(attribs, idx, idx + numChars));
idx += numChars;
// this iterates over every op string and decides which tags to open or to close
@@ -206,7 +217,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
const usedAttribs = [];
// mark all attribs as used
- for (const a of attributes.decodeAttribString(o.attribs)) {
+ for (const a of decodeAttribString(o.attribs)) {
if (a in anumMap) {
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
}
@@ -246,7 +257,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
// from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), '');
- assem.append(_encodeWhitespace(Security.escapeHTML(s)));
+ assem.append(_encodeWhitespace(escapeHTML(s)));
} // end iteration over spans in line
// close all the tags that are open after the last op
@@ -269,7 +280,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636
- assem.append(``);
+ assem.append(``);
processNextChars(urlLength);
assem.append('');
});
@@ -311,7 +322,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
if (i < textLines.length) {
nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
}
- await hooks.aCallAll('getLineHTMLForExport', context);
+ await aCallAll('getLineHTMLForExport', context);
// To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) ||
(line.listTypeName !== prevLine.listTypeName)) {
@@ -448,7 +459,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
padId: pad.id,
};
- await hooks.aCallAll('getLineHTMLForExport', context);
+ await aCallAll('getLineHTMLForExport', context);
pieces.push(context.lineContent, '
');
}
}
@@ -456,25 +467,25 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
return pieces.join('');
};
-exports.getPadHTMLDocument = async (padId, revNum, readOnlyId) => {
- const pad = await padManager.getPad(padId);
+export const getPadHTMLDocument = async (padId, revNum, readOnlyId?) => {
+ const pad = await getPad(padId);
// Include some Styles into the Head for Export
let stylesForExportCSS = '';
- const stylesForExport = await hooks.aCallAll('stylesForExport', padId);
+ const stylesForExport = await aCallAll('stylesForExport', padId);
stylesForExport.forEach((css) => {
stylesForExportCSS += css;
});
let html = await getPadHTML(pad, revNum);
- for (const hookHtml of await hooks.aCallAll('exportHTMLAdditionalContent', {padId})) {
+ for (const hookHtml of await aCallAll('exportHTMLAdditionalContent', {padId})) {
html += hookHtml;
}
- return eejs.require('ep_etherpad-lite/templates/export_html.html', {
+ return required('ep_etherpad-lite/templates/export_html.html', {
body: html,
- padId: Security.escapeHTML(readOnlyId || padId),
+ padId: escapeHTML(readOnlyId || padId),
extraCSS: stylesForExportCSS,
});
};
@@ -525,7 +536,4 @@ const _processSpaces = (s) => {
}
}
return parts.join('');
-};
-
-exports.getPadHTML = getPadHTML;
-exports.getHTMLFromAtext = getHTMLFromAtext;
+}
diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.ts
similarity index 97%
rename from src/node/utils/ExportTxt.js
rename to src/node/utils/ExportTxt.ts
index 9511dd0e7a3..7915725fa6d 100644
--- a/src/node/utils/ExportTxt.js
+++ b/src/node/utils/ExportTxt.ts
@@ -39,7 +39,7 @@ const getPadTXT = async (pad, revNum) => {
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
-const getTXTFromAtext = (pad, atext, authorColors) => {
+export const getTXTFromAtext = (pad, atext, authorColors?) => {
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
@@ -56,7 +56,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
});
const getLineTXT = (text, attribs) => {
- const propVals = [false, false, false];
+ const propVals:(boolean|number)[] = [false, false, false];
const ENTER = 1;
const STAY = 2;
const LEAVE = 0;
@@ -256,9 +256,8 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
return pieces.join('');
};
-exports.getTXTFromAtext = getTXTFromAtext;
-exports.getPadTXTDocument = async (padId, revNum) => {
+export const getPadTXTDocument = async (padId, revNum) => {
const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum);
};
diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.ts
similarity index 82%
rename from src/node/utils/ImportEtherpad.js
rename to src/node/utils/ImportEtherpad.ts
index da7e750ffb5..9258159915d 100644
--- a/src/node/utils/ImportEtherpad.js
+++ b/src/node/utils/ImportEtherpad.ts
@@ -16,30 +16,32 @@
* limitations under the License.
*/
-const AttributePool = require('../../static/js/AttributePool');
-const {Pad} = require('../db/Pad');
-const Stream = require('./Stream');
-const authorManager = require('../db/AuthorManager');
-const db = require('../db/DB');
-const hooks = require('../../static/js/pluginfw/hooks');
-const log4js = require('log4js');
-const supportedElems = require('../../static/js/contentcollector').supportedElems;
-const ueberdb = require('ueberdb2');
+import {AttributePool} from '../../static/js/AttributePool';
+import {Pad} from '../db/Pad';
+import {Stream} from './Stream';
+import {addPad, doesAuthorExist} from '../db/AuthorManager';
+import {db} from '../db/DB';
+import {aCallAll, callAll} from '../../static/js/pluginfw/hooks';
+import log4js from "log4js";
+
+import {supportedElems} from "../../static/js/contentcollector";
+
+import ueberdb from 'ueberdb2';
const logger = log4js.getLogger('ImportEtherpad');
-exports.setPadRaw = async (padId, r, authorId = '') => {
+export const setPadRaw = async (padId, r, authorId = '') => {
const records = JSON.parse(r);
// get supported block Elements from plugins, we will use this later.
- hooks.callAll('ccRegisterBlockElements').forEach((element) => {
+ callAll('ccRegisterBlockElements').forEach((element) => {
supportedElems.add(element);
});
// DB key prefixes for pad records. Each key is expected to have the form `${prefix}:${padId}` or
// `${prefix}:${padId}:${otherstuff}`.
const padKeyPrefixes = [
- ...await hooks.aCallAll('exportEtherpadAdditionalContent'),
+ ...await aCallAll('exportEtherpadAdditionalContent'),
'pad',
];
@@ -69,7 +71,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
throw new TypeError('globalAuthor padIDs subkey is not a string');
}
checkOriginalPadId(value.padIDs);
- if (await authorManager.doesAuthorExist(id)) {
+ if (await doesAuthorExist(id)) {
existingAuthors.add(id);
return;
}
@@ -101,7 +103,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
const pad = new Pad(padId, padDb);
await pad.init(null, authorId);
- await hooks.aCallAll('importEtherpad', {
+ await aCallAll('importEtherpad', {
pad,
// Shallow freeze meant to prevent accidental bugs. It would be better to deep freeze, but
// it's not worth the added complexity.
@@ -115,7 +117,7 @@ exports.setPadRaw = async (padId, r, authorId = '') => {
const writeOps = (function* () {
for (const [k, v] of data) yield db.set(k, v);
- for (const a of existingAuthors) yield authorManager.addPad(a, padId);
+ for (const a of existingAuthors) yield addPad(a as string, padId);
})();
for (const op of new Stream(writeOps).batch(100).buffer(99)) await op;
};
diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.ts
similarity index 83%
rename from src/node/utils/ImportHtml.js
rename to src/node/utils/ImportHtml.ts
index d7b2172b057..34dfb05e42e 100644
--- a/src/node/utils/ImportHtml.js
+++ b/src/node/utils/ImportHtml.ts
@@ -15,15 +15,15 @@
* limitations under the License.
*/
-const log4js = require('log4js');
-const Changeset = require('../../static/js/Changeset');
-const contentcollector = require('../../static/js/contentcollector');
-const jsdom = require('jsdom');
+import log4js from 'log4js';
+import {builder, deserializeOps} from '../../static/js/Changeset';
+import {makeContentCollector} from '../../static/js/contentcollector';
+import jsdom from 'jsdom';
const apiLogger = log4js.getLogger('ImportHtml');
let processor;
-exports.setPadHTML = async (pad, html, authorId = '') => {
+export const setPadHTML = async (pad, html, authorId = '') => {
if (processor == null) {
const [{rehype}, {default: minifyWhitespace}] =
await Promise.all([import('rehype'), import('rehype-minify-whitespace')]);
@@ -42,7 +42,7 @@ exports.setPadHTML = async (pad, html, authorId = '') => {
// Convert a dom tree into a list of lines and attribute liens
// using the content collector object
- const cc = contentcollector.makeContentCollector(true, null, pad.pool);
+ const cc = makeContentCollector(true, null, pad.pool);
try {
// we use a try here because if the HTML is bad it will blow up
cc.collectContent(document.body);
@@ -68,26 +68,26 @@ exports.setPadHTML = async (pad, html, authorId = '') => {
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
// create a new changeset with a helper builder object
- const builder = Changeset.builder(1);
+ const builder2 = builder(1);
// assemble each line into the builder
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = newText.length;
- for (const op of Changeset.deserializeOps(newAttribs)) {
+ for (const op of deserializeOps(newAttribs)) {
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
const start = Math.max(newTextStart, textIndex);
const end = Math.min(newTextEnd, nextIndex);
- builder.insert(newText.substring(start, end), op.attribs);
+ builder2.insert(newText.substring(start, end), op.attribs);
}
textIndex = nextIndex;
}
// the changeset is ready!
- const theChangeset = builder.toString();
+ const theChangeset = builder2.toString();
apiLogger.debug(`The changeset: ${theChangeset}`);
await pad.setText('\n', authorId);
await pad.appendRevision(theChangeset, authorId);
-};
+}
diff --git a/src/node/utils/LibreOffice.js b/src/node/utils/LibreOffice.ts
similarity index 91%
rename from src/node/utils/LibreOffice.js
rename to src/node/utils/LibreOffice.ts
index 33920919461..26f818d30ea 100644
--- a/src/node/utils/LibreOffice.js
+++ b/src/node/utils/LibreOffice.ts
@@ -17,20 +17,23 @@
* limitations under the License.
*/
-const async = require('async');
-const fs = require('fs').promises;
-const log4js = require('log4js');
-const os = require('os');
-const path = require('path');
-const runCmd = require('./run_cmd');
-const settings = require('./Settings');
+import async from 'async';
+import log4js from 'log4js';
+import {promises as fs} from "fs";
+
+import os from 'os';
+import path from "path";
+
+import exportCMD from "./run_cmd";
+
+import {soffice} from "./Settings";
const logger = log4js.getLogger('LibreOffice');
const doConvertTask = async (task) => {
const tmpDir = os.tmpdir();
- const p = runCmd([
- settings.soffice,
+ const p = await exportCMD([
+ soffice,
'--headless',
'--invisible',
'--nologo',
@@ -81,7 +84,7 @@ const queue = async.queue(doConvertTask, 1);
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
-exports.convertFile = async (srcFile, destFile, type) => {
+export const convertFile = async (srcFile, destFile, type) => {
// Used for the moving of the file, not the conversion
const fileExtension = type;
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.ts
similarity index 90%
rename from src/node/utils/Minify.js
rename to src/node/utils/Minify.ts
index 2e8a2d960fc..830bd9c1baf 100644
--- a/src/node/utils/Minify.js
+++ b/src/node/utils/Minify.ts
@@ -21,21 +21,28 @@
* limitations under the License.
*/
-const settings = require('./Settings');
-const fs = require('fs').promises;
-const path = require('path');
-const plugins = require('../../static/js/pluginfw/plugin_defs');
-const RequireKernel = require('etherpad-require-kernel');
-const mime = require('mime-types');
-const Threads = require('threads');
-const log4js = require('log4js');
+import {maxAge, root} from './Settings';
+import {promises as fs} from "fs";
+
+import path from "path";
+
+import {plugins} from "../../static/js/pluginfw/plugin_defs";
+
+import RequireKernel from "etherpad-require-kernel";
+
+import mime from "mime-types";
+
+import Threads from "threads";
+
+import log4js from "log4js";
+
const sanitizePathname = require('./sanitizePathname');
const logger = log4js.getLogger('Minify');
-const ROOT_DIR = path.join(settings.root, 'src/static/');
+const ROOT_DIR = path.join(root, 'src/static/');
-const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
+const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
const LIBRARY_WHITELIST = [
'async',
@@ -89,7 +96,7 @@ const requestURI = async (url, method, headers) => {
return await p;
};
-const requestURIs = (locations, method, headers, callback) => {
+export const requestURIs = (locations, method, headers, callback) => {
Promise.all(locations.map(async (loc) => {
try {
return await requestURI(loc, method, headers);
@@ -148,8 +155,8 @@ const minify = async (req, res) => {
const library = match[1];
const libraryPath = match[2] || '';
- if (plugins.plugins[library] && match[3]) {
- const plugin = plugins.plugins[library];
+ if (plugins[library] && match[3]) {
+ const plugin = plugins[library];
const pluginPath = plugin.package.realPath;
filename = path.join(pluginPath, libraryPath);
// On Windows, path.relative converts forward slashes to backslashes. Convert them back
@@ -176,10 +183,10 @@ const minify = async (req, res) => {
date.setMilliseconds(0);
res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString());
- if (settings.maxAge !== undefined) {
- const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
+ if (maxAge !== undefined) {
+ const expiresDate = new Date(Date.now() + maxAge * 1000);
res.setHeader('expires', expiresDate.toUTCString());
- res.setHeader('cache-control', `max-age=${settings.maxAge}`);
+ res.setHeader('cache-control', `max-age=${maxAge}`);
}
}
@@ -271,7 +278,7 @@ const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`
const getFileCompressed = async (filename, contentType) => {
let content = await getFile(filename);
- if (!content || !settings.minify) {
+ if (!content || !minify) {
return content;
} else if (contentType === 'application/javascript') {
return await new Promise((resolve) => {
@@ -317,10 +324,8 @@ const getFile = async (filename) => {
return await fs.readFile(path.resolve(ROOT_DIR, filename));
};
-exports.minify = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));
-
-exports.requestURIs = requestURIs;
+export const minifyExp = (req, res, next) => minify(req, res).catch((err) => next(err || new Error(err)));
-exports.shutdown = async (hookName, context) => {
+export const shutdown = async (hookName, context) => {
await threadsPool.terminate();
};
diff --git a/src/node/utils/MinifyWorker.js b/src/node/utils/MinifyWorker.ts
similarity index 80%
rename from src/node/utils/MinifyWorker.js
rename to src/node/utils/MinifyWorker.ts
index 364ecc96cca..f5307cc9a9d 100644
--- a/src/node/utils/MinifyWorker.js
+++ b/src/node/utils/MinifyWorker.ts
@@ -3,11 +3,14 @@
* Worker thread to minify JS & CSS files out of the main NodeJS thread
*/
-const CleanCSS = require('clean-css');
-const Terser = require('terser');
-const fsp = require('fs').promises;
-const path = require('path');
-const Threads = require('threads');
+import CleanCSS from 'clean-css';
+import Terser from "terser";
+
+import {promises as fsp} from "fs";
+
+import path from "path";
+
+import Threads from "threads";
const compressJS = (content) => Terser.minify(content);
diff --git a/src/node/utils/NodeVersion.js b/src/node/utils/NodeVersion.ts
similarity index 84%
rename from src/node/utils/NodeVersion.js
rename to src/node/utils/NodeVersion.ts
index ca5412f23c6..27de68d9b01 100644
--- a/src/node/utils/NodeVersion.js
+++ b/src/node/utils/NodeVersion.ts
@@ -26,19 +26,19 @@ const semver = require('semver');
*
* @param {String} minNodeVersion Minimum required Node version
*/
-exports.enforceMinNodeVersion = (minNodeVersion) => {
+const enforceMinNodeVersion = (minNodeVersion:string) => {
const currentNodeVersion = process.version;
// we cannot use template literals, since we still do not know if we are
// running under Node >= 4.0
if (semver.lt(currentNodeVersion, minNodeVersion)) {
console.error(`Running Etherpad on Node ${currentNodeVersion} is not supported. ` +
- `Please upgrade at least to Node ${minNodeVersion}`);
+ `Please upgrade at least to Node ${minNodeVersion}`);
process.exit(1);
}
console.debug(`Running on Node ${currentNodeVersion} ` +
- `(minimum required Node version: ${minNodeVersion})`);
+ `(minimum required Node version: ${minNodeVersion})`);
};
/**
@@ -49,7 +49,7 @@ exports.enforceMinNodeVersion = (minNodeVersion) => {
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated
* Node releases
*/
-exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion, epRemovalVersion) => {
+const checkDeprecationStatus = (lowestNonDeprecatedNodeVersion:string, epRemovalVersion:string) => {
const currentNodeVersion = process.version;
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {
@@ -58,3 +58,5 @@ exports.checkDeprecationStatus = (lowestNonDeprecatedNodeVersion, epRemovalVersi
`Please consider updating at least to Node ${lowestNonDeprecatedNodeVersion}`);
}
};
+
+export {checkDeprecationStatus, enforceMinNodeVersion}
diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.ts
similarity index 72%
rename from src/node/utils/Settings.js
rename to src/node/utils/Settings.ts
index 51f48237aa8..e785160143a 100644
--- a/src/node/utils/Settings.js
+++ b/src/node/utils/Settings.ts
@@ -27,18 +27,24 @@
* limitations under the License.
*/
-const absolutePaths = require('./AbsolutePaths');
-const deepEqual = require('fast-deep-equal/es6');
-const fs = require('fs');
-const os = require('os');
-const path = require('path');
-const argv = require('./Cli').argv;
-const jsonminify = require('jsonminify');
-const log4js = require('log4js');
-const randomString = require('./randomstring');
+import exp from "constants";
+// FIXME Is there a better way to enter dynamic package.json path
+// @ts-ignore
+import packageJSON from '../../package.json'
+import {findEtherpadRoot, makeAbsolute, isSubdir} from './AbsolutePaths';
+import deepEqual from 'fast-deep-equal/es6';
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import jsonminify from 'jsonminify';
+import log4js from 'log4js';
+import {LogLevel} from "../models/LogLevel";
+import {argv} from "./Cli";
+
+import {randomString} from './randomstring';
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
'suppressErrorsInPadText to true in your settings.json\n';
-const _ = require('underscore');
+import _ from 'underscore';
const logger = log4js.getLogger('settings');
@@ -50,15 +56,18 @@ const nonSettings = [
// This is a function to make it easy to create a new instance. It is important to not reuse a
// config object after passing it to log4js.configure() because that method mutates the object. :(
-const defaultLogConfig = () => ({appenders: [{type: 'console'}]});
+const defaultLogConfig = () => ({appenders: { console: { type: 'console' } },
+categories:{
+ default: { appenders: ['console'], level: 'info'}
+}});
const defaultLogLevel = 'INFO';
const initLogging = (logLevel, config) => {
// log4js.configure() modifies exports.logconfig so check for equality first.
const logConfigIsDefault = deepEqual(config, defaultLogConfig());
log4js.configure(config);
- log4js.setGlobalLogLevel(logLevel);
- log4js.replaceConsole();
+ log4js.getLogger("console");
+ console.log = logger.info.bind(logger)
// Log the warning after configuring log4js to increase the chances the user will see it.
if (!logConfigIsDefault) logger.warn('The logconfig setting is deprecated.');
};
@@ -68,16 +77,16 @@ const initLogging = (logLevel, config) => {
initLogging(defaultLogLevel, defaultLogConfig());
/* Root path of the installation */
-exports.root = absolutePaths.findEtherpadRoot();
+export const root = findEtherpadRoot();
logger.info('All relative paths will be interpreted relative to the identified ' +
- `Etherpad base dir: ${exports.root}`);
-exports.settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
-exports.credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
+ `Etherpad base dir: ${root}`);
+export const settingsFilename = makeAbsolute(argv.settings || 'settings.json');
+export const credentialsFilename = makeAbsolute(argv.credentials || 'credentials.json');
/**
* The app title, visible e.g. in the browser window
*/
-exports.title = 'Etherpad';
+export const title = 'Etherpad';
/**
* Pathname of the favicon you want to use. If null, the skin's favicon is
@@ -85,7 +94,7 @@ exports.title = 'Etherpad';
* is used. If this is a relative path it is interpreted as relative to the
* Etherpad root directory.
*/
-exports.favicon = null;
+export const favicon = null;
/*
* Skin name.
@@ -93,37 +102,43 @@ exports.favicon = null;
* Initialized to null, so we can spot an old configuration file and invite the
* user to update it before falling back to the default.
*/
-exports.skinName = null;
+export let skinName = null;
-exports.skinVariants = 'super-light-toolbar super-light-editor light-background';
+export const skinVariants = 'super-light-toolbar super-light-editor light-background';
/**
* The IP ep-lite should listen to
*/
-exports.ip = '0.0.0.0';
+export let ip:String = '0.0.0.0';
/**
* The Port ep-lite should listen to
*/
-exports.port = process.env.PORT || 9001;
+export let port = process.env.PORT || 9001;
/**
* Should we suppress Error messages from being in Pad Contents
*/
-exports.suppressErrorsInPadText = false;
+export const suppressErrorsInPadText = false;
/**
* The SSL signed server key and the Certificate Authority's own certificate
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
*/
-exports.ssl = false;
+export let ssl = false;
+
+export const sslKeys = {
+ cert: undefined,
+ key: undefined,
+ ca: undefined,
+}
/**
* socket.io transport methods
**/
-exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
+export const socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
-exports.socketIo = {
+export const socketIo = {
/**
* Maximum permitted client message size (in bytes).
*
@@ -138,20 +153,20 @@ exports.socketIo = {
/*
* The Type of the database
*/
-exports.dbType = 'dirty';
+export const dbType = 'dirty';
/**
* This setting is passed with dbType to ueberDB to set up the database
*/
-exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
+export const dbSettings = {filename: path.join(root, 'var/dirty.db')};
/**
* The default Text of a new pad
*/
-exports.defaultPadText = [
+export let defaultPadText = [
'Welcome to Etherpad!',
'',
'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' +
- 'text. This allows you to collaborate seamlessly on documents!',
+ 'text. This allows you to collaborate seamlessly on documents!',
'',
'Etherpad on Github: https://github.com/ether/etherpad-lite',
].join('\n');
@@ -159,7 +174,7 @@ exports.defaultPadText = [
/**
* The default Pad Settings for a user (Can be overridden by changing the setting
*/
-exports.padOptions = {
+export const padOptions = {
noColors: false,
showControls: true,
showChat: true,
@@ -176,7 +191,7 @@ exports.padOptions = {
/**
* Whether certain shortcut keys are enabled for a user in the pad
*/
-exports.padShortcutEnabled = {
+export const padShortcutEnabled = {
altF9: true,
altC: true,
delete: true,
@@ -204,7 +219,7 @@ exports.padShortcutEnabled = {
/**
* The toolbar buttons and order.
*/
-exports.toolbar = {
+export const toolbar = {
left: [
['bold', 'italic', 'underline', 'strikethrough'],
['orderedlist', 'unorderedlist', 'indent', 'outdent'],
@@ -224,92 +239,92 @@ exports.toolbar = {
/**
* A flag that requires any user to have a valid session (via the api) before accessing a pad
*/
-exports.requireSession = false;
+export const requireSession = false;
/**
* A flag that prevents users from creating new pads
*/
-exports.editOnly = false;
+export const editOnly = false;
/**
* Max age that responses will have (affects caching layer).
*/
-exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours
+export const maxAge = 1000 * 60 * 60 * 6; // 6 hours
/**
* A flag that shows if minification is enabled or not
*/
-exports.minify = true;
+export const minify = true;
/**
* The path of the abiword executable
*/
-exports.abiword = null;
+export let abiword = null;
/**
* The path of the libreoffice executable
*/
-exports.soffice = null;
+export let soffice = null;
/**
* The path of the tidy executable
*/
-exports.tidyHtml = null;
+export const tidyHtml = null;
/**
* Should we support none natively supported file types on import?
*/
-exports.allowUnknownFileEnds = true;
+export const allowUnknownFileEnds = true;
/**
* The log level of log4js
*/
-exports.loglevel = defaultLogLevel;
+export const loglevel:LogLevel = defaultLogLevel;
/**
* Disable IP logging
*/
-exports.disableIPlogging = false;
+export const disableIPlogging = false;
/**
* Number of seconds to automatically reconnect pad
*/
-exports.automaticReconnectionTimeout = 0;
+export const automaticReconnectionTimeout = 0;
/**
* Disable Load Testing
*/
-exports.loadTest = false;
+export const loadTest = false;
/**
* Disable dump of objects preventing a clean exit
*/
-exports.dumpOnUncleanExit = false;
+export const dumpOnUncleanExit = false;
/**
* Enable indentation on new lines
*/
-exports.indentationOnNewLine = true;
+export const indentationOnNewLine = true;
/*
* log4js appender configuration
*/
-exports.logconfig = defaultLogConfig();
+export const logconfig = defaultLogConfig();
/*
* Session Key, do not sure this.
*/
-exports.sessionKey = false;
+export let sessionKey: string|boolean = false;
/*
* Trust Proxy, whether or not trust the x-forwarded-for header.
*/
-exports.trustProxy = false;
+export let trustProxy = false;
/*
* Settings controlling the session cookie issued by Etherpad.
*/
-exports.cookie = {
+export const cookie = {
/*
* Value of the SameSite cookie property. "Lax" is recommended unless
* Etherpad will be embedded in an iframe from another site, in which case
@@ -331,20 +346,20 @@ exports.cookie = {
* authorization. Note: /admin always requires authentication, and
* either authorization by a module, or a user with is_admin set
*/
-exports.requireAuthentication = false;
-exports.requireAuthorization = false;
-exports.users = {};
+export const requireAuthentication = false;
+export const requireAuthorization = false;
+export let users = {};
/*
* Show settings in admin page, by default it is true
*/
-exports.showSettingsInAdminPage = true;
+export const showSettingsInAdminPage = true;
/*
* By default, when caret is moved out of viewport, it scrolls the minimum
* height needed to make this line visible.
*/
-exports.scrollWhenFocusLineIsOutOfViewport = {
+export const scrollWhenFocusLineIsOutOfViewport = {
/*
* Percentage of viewport height to be additionally scrolled.
*/
@@ -377,12 +392,16 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
*
* Do not enable on production machines.
*/
-exports.exposeVersion = false;
+export const exposeVersion = false;
/*
* Override any strings found in locale directories
*/
-exports.customLocaleStrings = {};
+export const customLocaleStrings = {};
+
+export const setUsers = (newUsers:any) => {
+ users = newUsers;
+}
/*
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
@@ -393,12 +412,13 @@ exports.customLocaleStrings = {};
*
* See https://github.com/nfriedly/express-rate-limit for more options
*/
-exports.importExportRateLimiting = {
+export let importExportRateLimiting = {
// duration of the rate limit window (milliseconds)
windowMs: 90000,
// maximum number of requests per IP to allow during the rate limit window
- max: 10,
+ max: 10, onLimitReached: undefined
+
};
/*
@@ -409,7 +429,7 @@ exports.importExportRateLimiting = {
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
-exports.commitRateLimiting = {
+export let commitRateLimiting = {
// duration of the rate limit window (seconds)
duration: 1,
@@ -423,34 +443,34 @@ exports.commitRateLimiting = {
*
* File size is specified in bytes. Default is 50 MB.
*/
-exports.importMaxFileSize = 50 * 1024 * 1024;
+export const importMaxFileSize = 50 * 1024 * 1024;
/*
* Disable Admin UI tests
*/
-exports.enableAdminUITests = false;
+export const enableAdminUITests = false;
// checks if abiword is avaiable
-exports.abiwordAvailable = () => {
- if (exports.abiword != null) {
+export const abiwordAvailable = () => {
+ if (abiword != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
-exports.sofficeAvailable = () => {
- if (exports.soffice != null) {
+export const sofficeAvailable = () => {
+ if (soffice != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else {
return 'no';
}
};
-exports.exportAvailable = () => {
- const abiword = exports.abiwordAvailable();
- const soffice = exports.sofficeAvailable();
+export const exportAvailable = () => {
+ const abiword = abiwordAvailable();
+ const soffice = sofficeAvailable();
if (abiword === 'no' && soffice === 'no') {
return 'no';
@@ -463,10 +483,10 @@ exports.exportAvailable = () => {
};
// Provide git version if available
-exports.getGitCommit = () => {
+export const getGitCommit = () => {
let version = '';
try {
- let rootPath = exports.root;
+ let rootPath = root;
if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim();
@@ -482,13 +502,14 @@ exports.getGitCommit = () => {
}
version = version.substring(0, 7);
} catch (e) {
- logger.warn(`Can't get git version for server header\n${e.message}`);
+ const errorCast = e as Error
+ logger.warn(`Can't get git version for server header\n${errorCast.message}`);
}
return version;
};
// Return etherpad version from package.json
-exports.getEpVersion = () => require('../../package.json').version;
+export const getEpVersion = () => packageJSON.version;
/**
* Receives a settingsObj and, if the property name is a valid configuration
@@ -497,7 +518,8 @@ exports.getEpVersion = () => require('../../package.json').version;
* This code refactors a previous version that copied & pasted the same code for
* both "settings.json" and "credentials.json".
*/
-const storeSettings = (settingsObj) => {
+//FIXME find out what settingsObj is
+const storeSettings = (settingsObj: any) => {
for (const i of Object.keys(settingsObj || {})) {
if (nonSettings.includes(i)) {
logger.warn(`Ignoring setting: '${i}'`);
@@ -536,9 +558,10 @@ const storeSettings = (settingsObj) => {
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
* in the literal string "null", instead.
*/
-const coerceValue = (stringValue) => {
+const coerceValue = (stringValue: string) => {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
- const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
+ const numberToEvaluate = Number(stringValue)
+ const isNumeric = !isNaN(numberToEvaluate) && !isNaN(parseFloat(stringValue)) && isFinite(numberToEvaluate);
if (isNumeric) {
// detected numeric string. Coerce to a number
@@ -591,7 +614,7 @@ const coerceValue = (stringValue) => {
*
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
*/
-const lookupEnvironmentVariables = (obj) => {
+const lookupEnvironmentVariables = (obj: any) => {
const stringifiedAndReplaced = JSON.stringify(obj, (key, value) => {
/*
* the first invocation of replacer() is with an empty key. Just go on, or
@@ -636,9 +659,9 @@ const lookupEnvironmentVariables = (obj) => {
if ((envVarValue === undefined) && (defaultValue === undefined)) {
logger.warn(`Environment variable "${envVarName}" does not contain any value for ` +
- `configuration key "${key}", and no default was given. Using null. ` +
- 'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' +
- 'explicitly use "null" as the default if you want to continue to use null.');
+ `configuration key "${key}", and no default was given. Using null. ` +
+ 'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' +
+ 'explicitly use "null" as the default if you want to continue to use null.');
/*
* We have to return null, because if we just returned undefined, the
@@ -649,9 +672,12 @@ const lookupEnvironmentVariables = (obj) => {
if ((envVarValue === undefined) && (defaultValue !== undefined)) {
logger.debug(`Environment variable "${envVarName}" not found for ` +
- `configuration key "${key}". Falling back to default value.`);
+ `configuration key "${key}". Falling back to default value.`);
return coerceValue(defaultValue);
+ } else if ((envVarValue === undefined))
+ {
+ return coerceValue("none")
}
// envVarName contained some value.
@@ -666,9 +692,7 @@ const lookupEnvironmentVariables = (obj) => {
return coerceValue(envVarValue);
});
- const newSettings = JSON.parse(stringifiedAndReplaced);
-
- return newSettings;
+ return JSON.parse(stringifiedAndReplaced);
};
/**
@@ -679,7 +703,7 @@ const lookupEnvironmentVariables = (obj) => {
*
* The isSettings variable only controls the error logging.
*/
-const parseSettings = (settingsFilename, isSettings) => {
+const parseSettings = (settingsFilename: string, isSettings:boolean) => {
let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction;
@@ -711,127 +735,129 @@ const parseSettings = (settingsFilename, isSettings) => {
logger.info(`${settingsType} loaded from: ${settingsFilename}`);
- const replacedSettings = lookupEnvironmentVariables(settings);
-
- return replacedSettings;
+ return lookupEnvironmentVariables(settings);
} catch (e) {
+ const error = e as Error
logger.error(`There was an error processing your ${settingsType} ` +
- `file from ${settingsFilename}: ${e.message}`);
+ `file from ${settingsFilename}: ${error.message}`);
process.exit(1);
}
-};
+}
+
-exports.reloadSettings = () => {
- const settings = parseSettings(exports.settingsFilename, true);
- const credentials = parseSettings(exports.credentialsFilename, false);
+export const reloadSettings = () => {
+ const settings = parseSettings(settingsFilename, true);
+ const credentials = parseSettings(credentialsFilename, false);
storeSettings(settings);
storeSettings(credentials);
- initLogging(exports.loglevel, exports.logconfig);
+ initLogging(loglevel, logconfig);
- if (!exports.skinName) {
+ if (!skinName) {
logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
- 'update your settings.json. Falling back to the default "colibris".');
- exports.skinName = 'colibris';
+ 'update your settings.json. Falling back to the default "colibris".');
+ skinName = 'colibris';
}
// checks if skinName has an acceptable value, otherwise falls back to "colibris"
- if (exports.skinName) {
- const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
- const countPieces = exports.skinName.split(path.sep).length;
+ if (skinName) {
+ const skinBasePath = path.join(root, 'src', 'static', 'skins');
+ const countPieces = skinName.split(path.sep).length;
if (countPieces !== 1) {
logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` +
- `not valid: "${exports.skinName}". Falling back to the default "colibris".`);
+ `not valid: "${skinName}". Falling back to the default "colibris".`);
- exports.skinName = 'colibris';
+ skinName = 'colibris';
}
// informative variable, just for the log messages
- let skinPath = path.join(skinBasePath, exports.skinName);
+ let skinPath = path.join(skinBasePath, skinName);
// what if someone sets skinName == ".." or "."? We catch him!
- if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
+ if (isSubdir(skinBasePath, skinPath) === false) {
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` +
- 'Falling back to the default "colibris".');
+ 'Falling back to the default "colibris".');
- exports.skinName = 'colibris';
- skinPath = path.join(skinBasePath, exports.skinName);
+ skinName = 'colibris';
+ skinPath = path.join(skinBasePath, skinName);
}
if (fs.existsSync(skinPath) === false) {
logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
- exports.skinName = 'colibris';
- skinPath = path.join(skinBasePath, exports.skinName);
+ skinName = 'colibris';
+ skinPath = path.join(skinBasePath, skinName);
}
- logger.info(`Using skin "${exports.skinName}" in dir: ${skinPath}`);
+ logger.info(`Using skin "${skinName}" in dir: ${skinPath}`);
}
- if (exports.abiword) {
+ if (abiword) {
// Check abiword actually exists
- if (exports.abiword != null) {
- fs.exists(exports.abiword, (exists) => {
+ if (abiword != null) {
+ fs.exists(abiword, (exists: boolean) => {
if (!exists) {
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`;
}
- logger.error(`${abiwordError} File location: ${exports.abiword}`);
- exports.abiword = null;
+ logger.error(`${abiwordError} File location: ${abiword}`);
+ abiword = null;
}
});
}
}
- if (exports.soffice) {
- fs.exists(exports.soffice, (exists) => {
+ if (soffice) {
+ fs.exists(soffice, (exists: boolean) => {
if (!exists) {
const sofficeError =
'soffice (libreoffice) does not exist at this path, check your settings file.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`;
}
- logger.error(`${sofficeError} File location: ${exports.soffice}`);
- exports.soffice = null;
+ logger.error(`${sofficeError} File location: ${soffice}`);
+ soffice = null;
}
});
}
- if (!exports.sessionKey) {
- const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
+ if (!sessionKey) {
+ const sessionkeyFilename = makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
try {
- exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
+ sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
logger.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch (e) {
logger.info(
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
- exports.sessionKey = randomString(32);
- fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8');
+ sessionKey = randomString(32);
+ // FIXME Check out why this can be string boolean or Array
+ // @ts-ignore
+ fs.writeFileSync(sessionkeyFilename, sessionKey, 'utf8');
}
} else {
logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' +
- 'This value is auto-generated now. Please remove the setting from the file. -- ' +
- 'If you are seeing this error after restarting using the Admin User ' +
- 'Interface then you can ignore this message.');
+ 'This value is auto-generated now. Please remove the setting from the file. -- ' +
+ 'If you are seeing this error after restarting using the Admin User ' +
+ 'Interface then you can ignore this message.');
}
- if (exports.dbType === 'dirty') {
+ if (dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
- if (!exports.suppressErrorsInPadText) {
- exports.defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
+ if (!suppressErrorsInPadText) {
+ defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
}
- exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
- logger.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
+ dbSettings.filename = makeAbsolute(dbSettings.filename);
+ logger.warn(`${dirtyWarning} File location: ${dbSettings.filename}`);
}
- if (exports.ip === '') {
+ if (ip === '') {
// using Unix socket for connectivity
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
- '"port" parameter will be interpreted as the path to a Unix socket to bind at.');
+ '"port" parameter will be interpreted as the path to a Unix socket to bind at.');
}
/*
@@ -845,13 +871,40 @@ exports.reloadSettings = () => {
* ACHTUNG: this may prevent caching HTTP proxies to work
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
*/
- exports.randomVersionString = randomString(4);
- logger.info(`Random string used for versioning assets: ${exports.randomVersionString}`);
+ const randomVersionStringWith4Chars = randomString(4);
+ logger.info(`Random string used for versioning assets: ${randomVersionStringWith4Chars}`);
+ return settings
};
-exports.exportedForTestingOnly = {
+export const exportedForTestingOnly = {
parseSettings,
};
// initially load settings
-exports.reloadSettings();
+reloadSettings();
+
+
+// Setters
+export const setPort = (value: number) => {
+ port = value;
+}
+
+export const setIp = (value: string) => {
+ ip = value;
+}
+
+export const setTrustProxy = (value: boolean) => {
+ trustProxy = value;
+}
+
+export const setSsl = (value: boolean) => {
+ ssl = value;
+}
+
+export const setimportExportRateLimiting = (value: any) => {
+ importExportRateLimiting = value;
+}
+
+export const setCommitRateLimiting = (value: any) => {
+ commitRateLimiting = value;
+}
diff --git a/src/node/utils/Stream.js b/src/node/utils/Stream.ts
similarity index 98%
rename from src/node/utils/Stream.js
rename to src/node/utils/Stream.ts
index 611b83b3372..0a083af8493 100644
--- a/src/node/utils/Stream.js
+++ b/src/node/utils/Stream.ts
@@ -4,7 +4,9 @@
* Wrapper around any iterable that adds convenience methods that standard JavaScript iterable
* objects lack.
*/
-class Stream {
+export class Stream {
+ private readonly _iter: any;
+ private _next: null;
/**
* @returns {Stream} A Stream that yields values in the half-open range [start, end).
*/
@@ -130,5 +132,3 @@ class Stream {
*/
[Symbol.iterator]() { return this._iter; }
}
-
-module.exports = Stream;
diff --git a/src/node/utils/TidyHtml.js b/src/node/utils/TidyHtml.ts
similarity index 80%
rename from src/node/utils/TidyHtml.js
rename to src/node/utils/TidyHtml.ts
index 5b48cdbad37..56fa68972ed 100644
--- a/src/node/utils/TidyHtml.js
+++ b/src/node/utils/TidyHtml.ts
@@ -3,16 +3,17 @@
* Tidy up the HTML in a given file
*/
-const log4js = require('log4js');
-const settings = require('./Settings');
-const spawn = require('child_process').spawn;
+import log4js from 'log4js';
+import {tidyHtml} from "./Settings";
-exports.tidy = (srcFile) => {
+import {spawn} from "child_process";
+
+export const tidy = (srcFile) => {
const logger = log4js.getLogger('TidyHtml');
return new Promise((resolve, reject) => {
// Don't do anything if Tidy hasn't been enabled
- if (!settings.tidyHtml) {
+ if (!tidyHtml) {
logger.debug('tidyHtml has not been configured yet, ignoring tidy request');
return resolve(null);
}
@@ -21,7 +22,7 @@ exports.tidy = (srcFile) => {
// Spawn a new tidy instance that cleans up the file inline
logger.debug(`Tidying ${srcFile}`);
- const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
+ const tidy = spawn(tidyHtml, ['-modify', srcFile]);
// Keep track of any error messages
tidy.stderr.on('data', (data) => {
diff --git a/src/node/utils/UpdateCheck.js b/src/node/utils/UpdateCheck.js
deleted file mode 100644
index cfbd9dad9fc..00000000000
--- a/src/node/utils/UpdateCheck.js
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict';
-const semver = require('semver');
-const settings = require('./Settings');
-const request = require('request');
-
-let infos;
-
-const loadEtherpadInformations = () => new Promise((resolve, reject) => {
- request('https://static.etherpad.org/info.json', (er, response, body) => {
- if (er) return reject(er);
-
- try {
- infos = JSON.parse(body);
- return resolve(infos);
- } catch (err) {
- return reject(err);
- }
- });
-});
-
-exports.getLatestVersion = () => {
- exports.needsUpdate();
- return infos.latestVersion;
-};
-
-exports.needsUpdate = (cb) => {
- loadEtherpadInformations().then((info) => {
- if (semver.gt(info.latestVersion, settings.getEpVersion())) {
- if (cb) return cb(true);
- }
- }).catch((err) => {
- console.error(`Can not perform Etherpad update check: ${err}`);
- if (cb) return cb(false);
- });
-};
-
-exports.check = () => {
- exports.needsUpdate((needsUpdate) => {
- if (needsUpdate) {
- console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
- }
- });
-};
diff --git a/src/node/utils/UpdateCheck.ts b/src/node/utils/UpdateCheck.ts
new file mode 100644
index 00000000000..b80bfb954d3
--- /dev/null
+++ b/src/node/utils/UpdateCheck.ts
@@ -0,0 +1,58 @@
+'use strict';
+import semver from 'semver';
+import {getEpVersion} from './Settings';
+import request from 'request';
+
+
+type InfoModel = {
+ latestVersion: string
+}
+
+let infos: InfoModel|undefined;
+
+const loadEtherpadInformations = () => new Promise((resolve, reject) => {
+ request('https://static.etherpad.org/info.json', (er, response, body) => {
+ if (er) reject(er);
+
+ try {
+ infos = JSON.parse(body);
+ if (infos === undefined|| infos === null){
+ reject("Could not retrieve current version")
+ return
+ }
+ resolve(infos);
+ } catch (err) {
+ reject(err);
+ }
+ });
+});
+
+const getLatestVersion = () => {
+ needsUpdate();
+ if(infos === undefined){
+ throw new Error("Could not retrieve latest version")
+ }
+
+ return infos.latestVersion;
+}
+
+export const needsUpdate = (cb?:(arg0: boolean)=>void) => {
+ loadEtherpadInformations().then((info) => {
+ if (semver.gt(info.latestVersion, getEpVersion())) {
+ if (cb) return cb(true);
+ }
+ }).catch((err) => {
+ console.error(`Can not perform Etherpad update check: ${err}`);
+ if (cb) return cb(false);
+ })
+}
+
+const check = () => {
+ needsUpdate((needsUpdate)=>{
+ if (needsUpdate) {
+ console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
+ }
+ })
+}
+
+export default {check, getLatestVersion}
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.ts
similarity index 90%
rename from src/node/utils/caching_middleware.js
rename to src/node/utils/caching_middleware.ts
index 3cc4daf271b..93512e1eec1 100644
--- a/src/node/utils/caching_middleware.js
+++ b/src/node/utils/caching_middleware.ts
@@ -16,15 +16,20 @@
* limitations under the License.
*/
-const Buffer = require('buffer').Buffer;
-const fs = require('fs');
-const fsp = fs.promises;
-const path = require('path');
-const zlib = require('zlib');
-const settings = require('./Settings');
-const existsSync = require('./path_exists');
-const util = require('util');
+import {Buffer} from "buffer";
+
+import fs, {Stats} from "fs";
+
+import path from "path";
+
+import {check} from './path_exists'
+import zlib from "zlib";
+
+import {root} from "./Settings";
+
+import util from "util";
+const fsp = fs.promises;
/*
* The crypto module can be absent on reduced node installations.
*
@@ -45,8 +50,8 @@ try {
_crypto = undefined;
}
-let CACHE_DIR = path.join(settings.root, 'var/');
-CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
+let CACHE_DIR = path.join(root, 'var/');
+CACHE_DIR = check(CACHE_DIR) ? CACHE_DIR : undefined;
const responseCache = {};
@@ -78,7 +83,7 @@ if (_crypto) {
should replace this.
*/
-module.exports = class CachingMiddleware {
+export class CachingMiddleware {
handle(req, res, next) {
this._handle(req, res, next).catch((err) => next(err || new Error(err)));
}
@@ -88,8 +93,15 @@ module.exports = class CachingMiddleware {
return next(undefined, req, res);
}
- const oldReq = {};
- const oldRes = {};
+ const oldReq = {
+ method: undefined
+ };
+ const oldRes = {
+ write: undefined,
+ end: undefined,
+ setHeader: undefined,
+ writeHead: undefined
+ };
const supportsGzip =
(req.get('Accept-Encoding') || '').indexOf('gzip') !== -1;
@@ -100,7 +112,7 @@ module.exports = class CachingMiddleware {
const stats = await fsp.stat(`${CACHE_DIR}minified_${cacheKey}`).catch(() => {});
const modifiedSince =
req.headers['if-modified-since'] && new Date(req.headers['if-modified-since']);
- if (stats != null && stats.mtime && responseCache[cacheKey]) {
+ if (stats != null && stats instanceof Object && "mtime" in stats && responseCache[cacheKey]) {
req.headers['if-modified-since'] = stats.mtime.toUTCString();
} else {
delete req.headers['if-modified-since'];
@@ -200,4 +212,4 @@ module.exports = class CachingMiddleware {
next(undefined, req, res);
}
-};
+}
diff --git a/src/node/utils/customError.js b/src/node/utils/customError.ts
similarity index 84%
rename from src/node/utils/customError.js
rename to src/node/utils/customError.ts
index 24ad181e6fa..b90946f65be 100644
--- a/src/node/utils/customError.js
+++ b/src/node/utils/customError.ts
@@ -7,7 +7,10 @@
* @class CustomError
* @extends {Error}
*/
-class CustomError extends Error {
+export class CustomError extends Error {
+ code: any;
+ signal: any;
+ easysync: boolean
/**
* Creates an instance of CustomError.
* @param {*} message
@@ -20,5 +23,3 @@ class CustomError extends Error {
Error.captureStackTrace(this, this.constructor);
}
}
-
-module.exports = CustomError;
diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.ts
similarity index 79%
rename from src/node/utils/padDiff.js
rename to src/node/utils/padDiff.ts
index 4ab276b4b66..737f830676a 100644
--- a/src/node/utils/padDiff.js
+++ b/src/node/utils/padDiff.ts
@@ -1,11 +1,21 @@
'use strict';
-const AttributeMap = require('../../static/js/AttributeMap');
-const Changeset = require('../../static/js/Changeset');
-const attributes = require('../../static/js/attributes');
-const exportHtml = require('./ExportHtml');
-
-function PadDiff(pad, fromRev, toRev) {
+import AttributeMap from '../../static/js/AttributeMap';
+import {
+ applyToAText,
+ builder, checkRep,
+ compose,
+ deserializeOps,
+ numToString, Op,
+ opAssembler, pack, splitAttributionLines, splitTextLines, stringAssembler,
+ unpack
+} from '../../static/js/Changeset';
+import {attribsFromString} from '../../static/js/attributes';
+import {getHTMLFromAtext} from './ExportHtml';
+// @ts-ignore
+import {PadDiffModel} from "ep_etherpad-lite/node/models/PadDiffModel";
+
+export const PadDiff = (pad, fromRev, toRev)=> {
// check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad');
@@ -14,16 +24,22 @@ function PadDiff(pad, fromRev, toRev) {
const range = pad.getValidRevisionRange(fromRev, toRev);
if (!range) throw new Error(`Invalid revision range. startRev: ${fromRev} endRev: ${toRev}`);
+ // FIXME How to fix this?
+ // @ts-ignore
this._pad = pad;
+ // @ts-ignore
this._fromRev = range.startRev;
+ // @ts-ignore
this._toRev = range.endRev;
+ // @ts-ignore
this._html = null;
+ // @ts-ignore
this._authors = [];
}
PadDiff.prototype._isClearAuthorship = function (changeset) {
// unpack
- const unpacked = Changeset.unpack(changeset);
+ const unpacked = unpack(changeset);
// check if there is nothing in the charBank
if (unpacked.charBank !== '') {
@@ -35,7 +51,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
return false;
}
- const [clearOperator, anotherOp] = Changeset.deserializeOps(unpacked.ops);
+ const [clearOperator, anotherOp] = deserializeOps(unpacked.ops);
// check if there is only one operator
if (anotherOp != null) return false;
@@ -52,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
}
const [appliedAttribute, anotherAttribute] =
- attributes.attribsFromString(clearOperator.attribs, this._pad.pool);
+ attribsFromString(clearOperator.attribs, this._pad.pool);
// Check that the operation has exactly one attribute.
if (appliedAttribute == null || anotherAttribute != null) return false;
@@ -69,9 +85,9 @@ PadDiff.prototype._createClearAuthorship = async function (rev) {
const atext = await this._pad.getInternalRevisionAText(rev);
// build clearAuthorship changeset
- const builder = Changeset.builder(atext.text.length);
- builder.keepText(atext.text, [['author', '']], this._pad.pool);
- const changeset = builder.toString();
+ const builder2 = builder(atext.text.length);
+ builder2.keepText(atext.text, [['author', '']], this._pad.pool);
+ const changeset = builder2.toString();
return changeset;
};
@@ -84,7 +100,7 @@ PadDiff.prototype._createClearStartAtext = async function (rev) {
const changeset = await this._createClearAuthorship(rev);
// apply the clearAuthorship changeset
- const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
+ const newAText = applyToAText(changeset, atext, this._pad.pool);
return newAText;
};
@@ -108,9 +124,11 @@ PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) {
return {changesets, authors};
};
-PadDiff.prototype._addAuthors = function (authors) {
- const self = this;
-
+PadDiff.prototype._addAuthors = (authors)=> {
+ let self: undefined|PadDiffModel = this;
+ if(!self){
+ self = {_authors: []}
+ }
// add to array if not in the array
authors.forEach((author) => {
if (self._authors.indexOf(author) === -1) {
@@ -151,7 +169,7 @@ PadDiff.prototype._createDiffAtext = async function () {
if (superChangeset == null) {
superChangeset = changeset;
} else {
- superChangeset = Changeset.compose(superChangeset, changeset, this._pad.pool);
+ superChangeset = compose(superChangeset, changeset, this._pad.pool);
}
}
@@ -165,10 +183,10 @@ PadDiff.prototype._createDiffAtext = async function () {
const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
// apply the superChangeset, which includes all addings
- atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool);
+ atext = applyToAText(superChangeset, atext, this._pad.pool);
// apply the deletionChangeset, which adds a deletions
- atext = Changeset.applyToAText(deletionChangeset, atext, this._pad.pool);
+ atext = applyToAText(deletionChangeset, atext, this._pad.pool);
}
return atext;
@@ -187,7 +205,7 @@ PadDiff.prototype.getHtml = async function () {
const authorColors = await this._pad.getAllAuthorColors();
// convert the atext to html
- this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors);
+ this._html = await getHTMLFromAtext(this._pad, atext, authorColors);
return this._html;
};
@@ -198,28 +216,32 @@ PadDiff.prototype.getAuthors = async function () {
if (this._html == null) {
await this.getHtml();
}
+ let self: undefined|PadDiffModel = this;
+ if(!self){
+ self = {_authors: []}
+ }
return self._authors;
};
PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
// unpack
- const unpacked = Changeset.unpack(changeset);
+ const unpacked = unpack(changeset);
- const assem = Changeset.opAssembler();
+ const assem = opAssembler();
// create deleted attribs
const authorAttrib = apool.putAttrib(['author', author || '']);
const deletedAttrib = apool.putAttrib(['removed', true]);
- const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
+ const attribs = `*${numToString(authorAttrib)}*${numToString(deletedAttrib)}`;
- for (const operator of Changeset.deserializeOps(unpacked.ops)) {
+ for (const operator of deserializeOps(unpacked.ops)) {
if (operator.opcode === '-') {
// this is a delete operator, extend it with the author
operator.attribs = attribs;
} else if (operator.opcode === '=' && operator.attribs) {
// this is operator changes only attributes, let's mark which author did that
- operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
+ operator.attribs += `*${numToString(authorAttrib)}`;
}
// append the new operator to our assembler
@@ -227,21 +249,21 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
}
// return the modified changeset
- return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
+ return pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
};
// this method is 80% like Changeset.inverse. I just changed so instead of reverting,
// it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
- const lines = Changeset.splitTextLines(startAText.text);
- const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
+ const lines = splitTextLines(startAText.text);
+ const alines = splitAttributionLines(startAText.attribs, startAText.text);
// lines and alines are what the exports is meant to apply to.
// They may be arrays or objects with .get(i) and .length methods.
// They include final newlines on lines.
const linesGet = (idx) => {
- if (lines.get) {
+ if ("get" in lines && lines.get instanceof Function) {
return lines.get(idx);
} else {
return lines[idx];
@@ -249,7 +271,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
};
const aLinesGet = (idx) => {
- if (alines.get) {
+ if ("get" in alines && alines.get instanceof Function) {
return alines.get(idx);
} else {
return alines[idx];
@@ -261,14 +283,14 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
let curLineOps = null;
let curLineOpsNext = null;
let curLineOpsLine;
- let curLineNextOp = new Changeset.Op('+');
+ let curLineNextOp = new Op('+');
- const unpacked = Changeset.unpack(cs);
- const builder = Changeset.builder(unpacked.newLen);
+ const unpacked = unpack(cs);
+ const builder2 = builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if (!curLineOps || curLineOpsLine !== curLine) {
- curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
+ curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine;
let indexIntoLine = 0;
@@ -289,13 +311,13 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
curChar = 0;
curLineOpsLine = curLine;
curLineNextOp.chars = 0;
- curLineOps = Changeset.deserializeOps(aLinesGet(curLine));
+ curLineOps = deserializeOps(aLinesGet(curLine));
curLineOpsNext = curLineOps.next();
}
if (!curLineNextOp.chars) {
if (curLineOpsNext.done) {
- curLineNextOp = new Changeset.Op();
+ curLineNextOp = new Op();
} else {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
@@ -330,7 +352,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const nextText = (numChars) => {
let len = 0;
- const assem = Changeset.stringAssembler();
+ const assem = stringAssembler();
const firstString = linesGet(curLine).substring(curChar);
len += firstString.length;
assem.append(firstString);
@@ -358,7 +380,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
};
};
- for (const csOp of Changeset.deserializeOps(unpacked.ops)) {
+ for (const csOp of deserializeOps(unpacked.ops)) {
if (csOp.opcode === '=') {
const textBank = nextText(csOp.chars);
@@ -404,7 +426,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
textLeftToProcess = textLeftToProcess.substr(lengthToProcess);
if (lineBreak) {
- builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
+ builder2.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
// consume the attributes of this linebreak
consumeAttribRuns(1, () => {});
@@ -416,31 +438,31 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
// get the old attributes back
const oldAttribs = undoBackToAttribs(attribs);
- builder.insert(processText.substr(textBankIndex, len), oldAttribs);
+ builder2.insert(processText.substr(textBankIndex, len), oldAttribs);
textBankIndex += len;
});
- builder.keep(lengthToProcess, 0);
+ builder2.keep(lengthToProcess, 0);
}
}
} else {
skip(csOp.chars, csOp.lines);
- builder.keep(csOp.chars, csOp.lines);
+ builder2.keep(csOp.chars, csOp.lines);
}
} else if (csOp.opcode === '+') {
- builder.keep(csOp.chars, csOp.lines);
+ builder2.keep(csOp.chars, csOp.lines);
} else if (csOp.opcode === '-') {
const textBank = nextText(csOp.chars);
let textBankIndex = 0;
consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
- builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
+ builder2.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
textBankIndex += len;
});
}
}
- return Changeset.checkRep(builder.toString());
+ return checkRep(builder.toString());
};
// export the constructor
diff --git a/src/node/utils/path_exists.js b/src/node/utils/path_exists.ts
similarity index 71%
rename from src/node/utils/path_exists.js
rename to src/node/utils/path_exists.ts
index 0b4c8fe94fc..91b6dd12726 100644
--- a/src/node/utils/path_exists.js
+++ b/src/node/utils/path_exists.ts
@@ -1,7 +1,7 @@
'use strict';
-const fs = require('fs');
+import fs from 'fs';
-const check = (path) => {
+export const check = (path) => {
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
let result;
@@ -11,6 +11,4 @@ const check = (path) => {
result = false;
}
return result;
-};
-
-module.exports = check;
+}
diff --git a/src/node/utils/promises.js b/src/node/utils/promises.ts
similarity index 93%
rename from src/node/utils/promises.js
rename to src/node/utils/promises.ts
index bc9f8c2dc06..9ebb07da4c5 100644
--- a/src/node/utils/promises.js
+++ b/src/node/utils/promises.ts
@@ -7,7 +7,7 @@
// `predicate`. Resolves to `undefined` if none of the Promises satisfy `predicate`, or if
// `promises` is empty. If `predicate` is nullish, the truthiness of the resolved value is used as
// the predicate.
-exports.firstSatisfies = (promises, predicate) => {
+export const firstSatisfies = (promises, predicate) => {
if (predicate == null) predicate = (x) => x;
// Transform each original Promise into a Promise that never resolves if the original resolved
@@ -42,7 +42,7 @@ exports.firstSatisfies = (promises, predicate) => {
// `total` is greater than `concurrency`, then `concurrency` Promises will be created right away,
// and each remaining Promise will be created once one of the earlier Promises resolves.) This async
// function resolves once all `total` Promises have resolved.
-exports.timesLimit = async (total, concurrency, promiseCreator) => {
+export const timesLimit = async (total, concurrency, promiseCreator) => {
if (total > 0 && concurrency <= 0) throw new RangeError('concurrency must be positive');
let next = 0;
const addAnother = () => promiseCreator(next++).finally(() => {
@@ -55,11 +55,14 @@ exports.timesLimit = async (total, concurrency, promiseCreator) => {
await Promise.all(promises);
};
+
/**
* An ordinary Promise except the `resolve` and `reject` executor functions are exposed as
* properties.
*/
-class Gate extends Promise {
+//FIXME Why is the constructor diviating from the Promise constructor?
+// @ts-ignore
+export class Gate extends Promise {
// Coax `.then()` into returning an ordinary Promise, not a Gate. See
// https://stackoverflow.com/a/65669070 for the rationale.
static get [Symbol.species]() { return Promise; }
@@ -73,4 +76,3 @@ class Gate extends Promise {
Object.assign(this, props);
}
}
-exports.Gate = Gate;
diff --git a/src/node/utils/randomstring.js b/src/node/utils/randomstring.ts
similarity index 50%
rename from src/node/utils/randomstring.js
rename to src/node/utils/randomstring.ts
index 4ffd3e8ae77..152239a1955 100644
--- a/src/node/utils/randomstring.js
+++ b/src/node/utils/randomstring.ts
@@ -3,8 +3,6 @@
* Generates a random String with the given length. Is needed to generate the
* Author, Group, readonly, session Ids
*/
-const crypto = require('crypto');
+import crypto from 'crypto'
-const randomString = (len) => crypto.randomBytes(len).toString('hex');
-
-module.exports = randomString;
+export const randomString = (len) => crypto.randomBytes(len).toString('hex');
diff --git a/src/node/utils/run_cmd.js b/src/node/utils/run_cmd.ts
similarity index 84%
rename from src/node/utils/run_cmd.js
rename to src/node/utils/run_cmd.ts
index bf5515c8466..a4ee806fe92 100644
--- a/src/node/utils/run_cmd.js
+++ b/src/node/utils/run_cmd.ts
@@ -1,12 +1,22 @@
+
'use strict';
-const spawn = require('cross-spawn');
-const log4js = require('log4js');
-const path = require('path');
-const settings = require('./Settings');
+import spawn from 'cross-spawn';
+import log4js from 'log4js';
+import path from 'path';
+import {root} from './Settings';
+import {CustomError} from "./customError";
const logger = log4js.getLogger('runCmd');
+type CMDPromise = {
+ stdout: Promise,
+ stderr: Promise,
+ status: Promise,
+ signal: Promise,
+}
+
+
const logLines = (readable, logLineFn) => {
readable.setEncoding('utf8');
// The process won't necessarily write full lines every time -- it might write a part of a line
@@ -69,18 +79,30 @@ const logLines = (readable, logLineFn) => {
* - `stderr`: Similar to `stdout` but for stderr.
* - `child`: The ChildProcess object.
*/
-module.exports = exports = (args, opts = {}) => {
- logger.debug(`Executing command: ${args.join(' ')}`);
- opts = {cwd: settings.root, ...opts};
- logger.debug(`cwd: ${opts.cwd}`);
+type RunOpts = {
+ cwd?: string;
+ env?: any;
+ stdio?: any;
+ detached?: boolean;
+ uid?: number
+}
+
+
+const run_cmd = (args, opts:RunOpts = {
+ stdio: undefined
+}) => {
+ logger.info(`Executing command: ${args.join(' ')}`);
+
+ opts = {cwd: root, ...opts};
+ logger.info(`cwd: ${opts.cwd}`);
// Log stdout and stderr by default.
const stdio =
Array.isArray(opts.stdio) ? opts.stdio.slice() // Copy to avoid mutating the caller's array.
- : typeof opts.stdio === 'function' ? [null, opts.stdio, opts.stdio]
- : opts.stdio === 'string' ? [null, 'string', 'string']
- : Array(3).fill(opts.stdio);
+ : typeof opts.stdio === 'function' ? [null, opts.stdio, opts.stdio]
+ : opts.stdio === 'string' ? [null, 'string', 'string']
+ : Array(3).fill(opts.stdio);
const cmdLogger = log4js.getLogger(`runCmd|${args[0]}`);
if (stdio[1] == null) stdio[1] = (line) => cmdLogger.info(line);
if (stdio[2] == null) stdio[2] = (line) => cmdLogger.error(line);
@@ -107,8 +129,8 @@ module.exports = exports = (args, opts = {}) => {
opts.env = {
...env, // Copy env to avoid modifying process.env or the caller's supplied env.
[pathVarName]: [
- path.join(settings.root, 'src', 'node_modules', '.bin'),
- path.join(settings.root, 'node_modules', '.bin'),
+ path.join(root, 'src', 'node_modules', '.bin'),
+ path.join(root, 'node_modules', '.bin'),
...(PATH ? PATH.split(path.delimiter) : []),
].join(path.delimiter),
};
@@ -116,14 +138,16 @@ module.exports = exports = (args, opts = {}) => {
// Create an error object to use in case the process fails. This is done here rather than in the
// process's `exit` handler so that we get a useful stack trace.
- const procFailedErr = new Error();
+ const procFailedErr = new CustomError({});
const proc = spawn(args[0], args.slice(1), opts);
const streams = [undefined, proc.stdout, proc.stderr];
let px;
- const p = new Promise((resolve, reject) => { px = {resolve, reject}; });
+ const p = new Promise((resolve, reject) => { px = {resolve, reject}; });
+ // @ts-ignore
[, p.stdout, p.stderr] = streams;
+ // @ts-ignore
p.child = proc;
const stdioStringPromises = [undefined, Promise.resolve(), Promise.resolve()];
@@ -132,6 +156,7 @@ module.exports = exports = (args, opts = {}) => {
if (stdioLoggers[fd] != null) {
logLines(streams[fd], stdioLoggers[fd]);
} else if (stdioSaveString[fd]) {
+ // @ts-ignore
p[[null, 'stdout', 'stderr'][fd]] = stdioStringPromises[fd] = (async () => {
const chunks = [];
for await (const chunk of streams[fd]) chunks.push(chunk);
@@ -155,3 +180,4 @@ module.exports = exports = (args, opts = {}) => {
});
return p;
};
+export default run_cmd;
diff --git a/src/node/utils/sanitizePathname.js b/src/node/utils/sanitizePathname.ts
similarity index 94%
rename from src/node/utils/sanitizePathname.js
rename to src/node/utils/sanitizePathname.ts
index 61b61116661..b9e492661a3 100644
--- a/src/node/utils/sanitizePathname.js
+++ b/src/node/utils/sanitizePathname.ts
@@ -1,10 +1,10 @@
'use strict';
-const path = require('path');
+import path from 'path';
// Normalizes p and ensures that it is a relative path that does not reach outside. See
// https://nvd.nist.gov/vuln/detail/CVE-2015-3297 for additional context.
-module.exports = (p, pathApi = path) => {
+export default (p, pathApi = path) => {
// The documentation for path.normalize() says that it resolves '..' and '.' segments. The word
// "resolve" implies that it examines the filesystem to resolve symbolic links, so 'a/../b' might
// not be the same thing as 'b'. Most path normalization functions from other libraries (e.g.,
@@ -20,4 +20,4 @@ module.exports = (p, pathApi = path) => {
// pathname would not be normalized away before being converted to '../'.
if (pathApi.sep === '\\') p = p.replace(/\\/g, '/');
return p;
-};
+}
diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.ts
similarity index 98%
rename from src/node/utils/toolbar.js
rename to src/node/utils/toolbar.ts
index 40a47687818..57aed942f31 100644
--- a/src/node/utils/toolbar.js
+++ b/src/node/utils/toolbar.ts
@@ -2,7 +2,7 @@
/**
* The Toolbar Module creates and renders the toolbars and buttons
*/
-const _ = require('underscore');
+import _ from 'underscore';
const removeItem = (array, what) => {
let ax;
@@ -12,13 +12,13 @@ const removeItem = (array, what) => {
return array;
};
-const defaultButtonAttributes = (name, overrides) => ({
+const defaultButtonAttributes = (name, overrides?) => ({
command: name,
localizationId: `pad.toolbar.${name}.title`,
class: `buttonicon buttonicon-${name}`,
});
-const tag = (name, attributes, contents) => {
+const tag = (name, attributes, contents?) => {
const aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) {
diff --git a/src/package-lock.json b/src/package-lock.json
index abf18216eb6..c421274846f 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -4,6 +4,22 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true
+ },
+ "@ampproject/remapping": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
"@apidevtools/json-schema-ref-parser": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz",
@@ -14,13 +30,6 @@
"@types/lodash.clonedeep": "^4.5.7",
"js-yaml": "^4.1.0",
"lodash.clonedeep": "^4.5.0"
- },
- "dependencies": {
- "@types/json-schema": {
- "version": "7.0.12",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
- "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
- }
}
},
"@azure/abort-controller": {
@@ -28,350 +37,1669 @@
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
"integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
"requires": {
- "tslib": "^2.2.0"
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-auth": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz",
+ "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-client": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz",
+ "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-auth": "^1.4.0",
+ "@azure/core-rest-pipeline": "^1.9.1",
+ "@azure/core-tracing": "^1.0.0",
+ "@azure/core-util": "^1.0.0",
+ "@azure/logger": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-http-compat": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz",
+ "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.4",
+ "@azure/core-client": "^1.3.0",
+ "@azure/core-rest-pipeline": "^1.3.0"
+ }
+ },
+ "@azure/core-lro": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz",
+ "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-util": "^1.2.0",
+ "@azure/logger": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-paging": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
+ "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-rest-pipeline": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.11.0.tgz",
+ "integrity": "sha512-nB4KXl6qAyJmBVLWA7SakT4tzpYZTCk4pvRBeI+Ye0WYSOrlTqlMhc4MSS/8atD3ufeYWdkN380LLoXlUUzThw==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-auth": "^1.4.0",
+ "@azure/core-tracing": "^1.0.1",
+ "@azure/core-util": "^1.3.0",
+ "@azure/logger": "^1.0.0",
+ "form-data": "^4.0.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-tracing": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz",
+ "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/core-util": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
+ "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/identity": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz",
+ "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-auth": "^1.3.0",
+ "@azure/core-client": "^1.4.0",
+ "@azure/core-rest-pipeline": "^1.1.0",
+ "@azure/core-tracing": "^1.0.0",
+ "@azure/core-util": "^1.0.0",
+ "@azure/logger": "^1.0.0",
+ "@azure/msal-browser": "^2.26.0",
+ "@azure/msal-common": "^7.0.0",
+ "@azure/msal-node": "^1.10.0",
+ "events": "^3.0.0",
+ "jws": "^4.0.0",
+ "open": "^8.0.0",
+ "stoppable": "^1.1.0",
+ "tslib": "^2.2.0",
+ "uuid": "^8.3.0"
+ },
+ "dependencies": {
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ }
+ }
+ },
+ "@azure/keyvault-keys": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.1.tgz",
+ "integrity": "sha512-zfmlZQCw1Yz+aPhgZmWOYBUzaKmfBzR2yceAE4S6hKDl7YZraTguuXmtFbCqjRvpz+pIMKAK25fENay9mFy1hQ==",
+ "requires": {
+ "@azure/abort-controller": "^1.0.0",
+ "@azure/core-auth": "^1.3.0",
+ "@azure/core-client": "^1.5.0",
+ "@azure/core-http-compat": "^1.3.0",
+ "@azure/core-lro": "^2.2.0",
+ "@azure/core-paging": "^1.1.1",
+ "@azure/core-rest-pipeline": "^1.8.0",
+ "@azure/core-tracing": "^1.0.0",
+ "@azure/core-util": "^1.0.0",
+ "@azure/logger": "^1.0.0",
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/logger": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
+ "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
+ "@azure/msal-browser": {
+ "version": "2.37.1",
+ "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.37.1.tgz",
+ "integrity": "sha512-EoKQISEpIY39Ru1OpWkeFZBcwp6Y0bG81bVmdyy4QJebPPDdVzfm62PSU0XFIRc3bqjZ4PBKBLMYLuo9NZYAow==",
+ "requires": {
+ "@azure/msal-common": "13.1.0"
+ },
+ "dependencies": {
+ "@azure/msal-common": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.1.0.tgz",
+ "integrity": "sha512-wj+ULrRB0HTuMmtrMjg8j3guCx32GE2BCPbsMCZkHgL1BZetC3o/Su5UJEQMX1HNc9CrIaQNx5WaKWHygYDe0g=="
+ }
+ }
+ },
+ "@azure/msal-common": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz",
+ "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q=="
+ },
+ "@azure/msal-node": {
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.17.3.tgz",
+ "integrity": "sha512-slsa+388bQQWnWH1V91KL+zV57rIp/0OQFfF0EmVMY8gnEIkAnpWWFUVBTTMbxEyjEFMk5ZW9xiHvHBcYFHzDw==",
+ "requires": {
+ "@azure/msal-common": "13.1.0",
+ "jsonwebtoken": "^9.0.0",
+ "uuid": "^8.3.0"
+ },
+ "dependencies": {
+ "@azure/msal-common": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.1.0.tgz",
+ "integrity": "sha512-wj+ULrRB0HTuMmtrMjg8j3guCx32GE2BCPbsMCZkHgL1BZetC3o/Su5UJEQMX1HNc9CrIaQNx5WaKWHygYDe0g=="
+ },
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ }
+ }
+ },
+ "@babel/cli": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.6.tgz",
+ "integrity": "sha512-Be3/RfEDmkMRGT1+ru5nTkfcvWz5jDOYg1V9rXqTz2u9Qt96O1ryboGvxVBp7wOnYWDB8DNHIWb6DThrpudfOw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
+ "chokidar": "^3.4.0",
+ "commander": "^4.0.1",
+ "convert-source-map": "^1.1.0",
+ "fs-readdir-recursive": "^1.1.0",
+ "glob": "^7.2.0",
+ "make-dir": "^2.1.0",
+ "slash": "^2.0.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+ "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.22.5"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
+ "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
+ "dev": true
+ },
+ "@babel/core": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz",
+ "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==",
+ "dev": true,
+ "requires": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.22.5",
+ "@babel/generator": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helpers": "^7.22.6",
+ "@babel/parser": "^7.22.6",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.6",
+ "@babel/types": "^7.22.5",
+ "@nicolo-ribaudo/semver-v6": "^6.3.3",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.2"
+ },
+ "dependencies": {
+ "@nicolo-ribaudo/semver-v6": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+ "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
+ "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
+ "jsesc": "^2.5.1"
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+ "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz",
+ "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz",
+ "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-validator-option": "^7.22.5",
+ "@nicolo-ribaudo/semver-v6": "^6.3.3",
+ "browserslist": "^4.21.9",
+ "lru-cache": "^5.1.1"
+ },
+ "dependencies": {
+ "@nicolo-ribaudo/semver-v6": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+ "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz",
+ "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@nicolo-ribaudo/semver-v6": "^6.3.3"
+ },
+ "dependencies": {
+ "@nicolo-ribaudo/semver-v6": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+ "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-create-regexp-features-plugin": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz",
+ "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@nicolo-ribaudo/semver-v6": "^6.3.3",
+ "regexpu-core": "^5.3.1"
+ },
+ "dependencies": {
+ "@nicolo-ribaudo/semver-v6": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+ "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-define-polyfill-provider": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz",
+ "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-compilation-targets": "^7.17.7",
+ "@babel/helper-plugin-utils": "^7.16.7",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2",
+ "semver": "^6.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
+ "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
+ "dev": true
+ },
+ "@babel/helper-function-name": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
+ "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+ "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz",
+ "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
+ "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz",
+ "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+ "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+ "dev": true
+ },
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz",
+ "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-wrap-function": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz",
+ "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-member-expression-to-functions": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+ "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+ "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+ "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helper-string-parser": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+ "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
+ "dev": true
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+ "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
+ "dev": true
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
+ "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
+ "dev": true
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz",
+ "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.5",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
+ "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.22.5",
+ "@babel/traverse": "^7.22.6",
+ "@babel/types": "^7.22.5"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+ "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@babel/parser": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz",
+ "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==",
+ "dev": true
+ },
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz",
+ "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz",
+ "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.22.5"
+ }
+ },
+ "@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "dev": true
+ },
+ "@babel/plugin-proposal-unicode-property-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
+ "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ }
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ }
+ },
+ "@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-export-namespace-from": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+ "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-import-assertions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz",
+ "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-syntax-import-attributes": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz",
+ "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ }
+ },
+ "@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz",
+ "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-async-generator-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz",
+ "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.5",
+ "@babel/plugin-syntax-async-generators": "^7.8.4"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz",
+ "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-remap-async-to-generator": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz",
+ "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz",
+ "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-class-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz",
+ "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-class-static-block": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz",
+ "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz",
+ "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-optimise-call-expression": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "globals": "^11.1.0"
+ },
+ "dependencies": {
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz",
+ "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/template": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz",
+ "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz",
+ "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz",
+ "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-dynamic-import": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz",
+ "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz",
+ "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-export-namespace-from": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz",
+ "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz",
+ "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz",
+ "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-compilation-targets": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-json-strings": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz",
+ "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-json-strings": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz",
+ "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz",
+ "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz",
+ "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz",
+ "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz",
+ "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-simple-access": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz",
+ "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz",
+ "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
+ "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz",
+ "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz",
+ "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-numeric-separator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz",
+ "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-object-rest-spread": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz",
+ "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-transform-parameters": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz",
+ "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-replace-supers": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz",
+ "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-optional-chaining": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz",
+ "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3"
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz",
+ "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-private-methods": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz",
+ "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-private-property-in-object": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz",
+ "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.22.5",
+ "@babel/helper-create-class-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz",
+ "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz",
+ "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "regenerator-transform": "^0.15.1"
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz",
+ "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-shorthand-properties": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz",
+ "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz",
+ "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz",
+ "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-asynciterator-polyfill": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.2.tgz",
- "integrity": "sha512-3rkP4LnnlWawl0LZptJOdXNrT/fHp2eQMadoasa6afspXdpGrtPZuAQc2PD0cpgyuoXtUWyC3tv7xfntjGS5Dw=="
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz",
+ "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.22.5"
+ }
},
- "@azure/core-auth": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.2.tgz",
- "integrity": "sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA==",
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz",
+ "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "tslib": "^2.2.0"
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-client": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.6.0.tgz",
- "integrity": "sha512-YhSf4cb61ApSjItscp9XoaLq8KRnacPDAhmjAZSMnn/gs6FhFbZNfOBOErG2dDj7JRknVtCmJ5mLmfR2sLa11A==",
+ "@babel/plugin-transform-unicode-escapes": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz",
+ "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-auth": "^1.3.0",
- "@azure/core-rest-pipeline": "^1.5.0",
- "@azure/core-tracing": "^1.0.0",
- "@azure/core-util": "^1.0.0",
- "@azure/logger": "^1.0.0",
- "tslib": "^2.2.0"
- },
- "dependencies": {
- "@azure/core-tracing": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz",
- "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==",
- "requires": {
- "tslib": "^2.2.0"
- }
- }
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-http": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.2.tgz",
- "integrity": "sha512-Z4dfbglV9kNZO177CNx4bo5ekFuYwwsvjLiKdZI4r84bYGv3irrbQz7JC3/rUfFH2l4T/W6OFleJaa2X0IaQqw==",
+ "@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz",
+ "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-auth": "^1.3.0",
- "@azure/core-tracing": "1.0.0-preview.13",
- "@azure/core-util": "^1.1.1",
- "@azure/logger": "^1.0.0",
- "@types/node-fetch": "^2.5.0",
- "@types/tunnel": "^0.0.3",
- "form-data": "^4.0.0",
- "node-fetch": "^2.6.7",
- "process": "^0.11.10",
- "tough-cookie": "^4.0.0",
- "tslib": "^2.2.0",
- "tunnel": "^0.0.6",
- "uuid": "^8.3.0",
- "xml2js": "^0.5.0"
- },
- "dependencies": {
- "@azure/core-util": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
- "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
- "requires": {
- "@azure/abort-controller": "^1.0.0",
- "tslib": "^2.2.0"
- }
- },
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
- },
- "xml2js": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
- "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
- "requires": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- }
- }
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-lro": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.2.4.tgz",
- "integrity": "sha512-e1I2v2CZM0mQo8+RSix0x091Av493e4bnT22ds2fcQGslTHzM2oTbswkB65nP4iEpCxBrFxOSDPKExmTmjCVtQ==",
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz",
+ "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-tracing": "1.0.0-preview.13",
- "@azure/logger": "^1.0.0",
- "tslib": "^2.2.0"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-paging": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.2.1.tgz",
- "integrity": "sha512-UtH5iMlYsvg+nQYIl4UHlvvSrsBjOlRF4fs0j7mxd3rWdAStrKYrh2durOpHs5C9yZbVhsVDaisoyaf/lL1EVA==",
+ "@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz",
+ "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==",
+ "dev": true,
"requires": {
- "@azure/core-asynciterator-polyfill": "^1.0.0",
- "tslib": "^2.2.0"
+ "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.22.5"
}
},
- "@azure/core-rest-pipeline": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.8.1.tgz",
- "integrity": "sha512-R/XpxZcDgGbnneEifnsAcjLoR2NCmlDxKDmzd8oi5jx5YEnPE6gsxHQWAk2+uY55Ka717x/fdctyoCYKnumrqw==",
+ "@babel/preset-env": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.6.tgz",
+ "integrity": "sha512-IHr0AXHGk8oh8HYSs45Mxuv6iySUBwDTIzJSnXN7PURqHdxJVQlCoXmKJgyvSS9bcNf9NVRVE35z+LkCvGmi6w==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-auth": "^1.3.0",
- "@azure/core-tracing": "^1.0.1",
- "@azure/core-util": "^1.0.0",
- "@azure/logger": "^1.0.0",
- "form-data": "^4.0.0",
- "http-proxy-agent": "^4.0.1",
- "https-proxy-agent": "^5.0.0",
- "tslib": "^2.2.0",
- "uuid": "^8.3.0"
+ "@babel/compat-data": "^7.22.6",
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-validator-option": "^7.22.5",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+ "@babel/plugin-syntax-import-assertions": "^7.22.5",
+ "@babel/plugin-syntax-import-attributes": "^7.22.5",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.22.5",
+ "@babel/plugin-transform-async-generator-functions": "^7.22.5",
+ "@babel/plugin-transform-async-to-generator": "^7.22.5",
+ "@babel/plugin-transform-block-scoped-functions": "^7.22.5",
+ "@babel/plugin-transform-block-scoping": "^7.22.5",
+ "@babel/plugin-transform-class-properties": "^7.22.5",
+ "@babel/plugin-transform-class-static-block": "^7.22.5",
+ "@babel/plugin-transform-classes": "^7.22.6",
+ "@babel/plugin-transform-computed-properties": "^7.22.5",
+ "@babel/plugin-transform-destructuring": "^7.22.5",
+ "@babel/plugin-transform-dotall-regex": "^7.22.5",
+ "@babel/plugin-transform-duplicate-keys": "^7.22.5",
+ "@babel/plugin-transform-dynamic-import": "^7.22.5",
+ "@babel/plugin-transform-exponentiation-operator": "^7.22.5",
+ "@babel/plugin-transform-export-namespace-from": "^7.22.5",
+ "@babel/plugin-transform-for-of": "^7.22.5",
+ "@babel/plugin-transform-function-name": "^7.22.5",
+ "@babel/plugin-transform-json-strings": "^7.22.5",
+ "@babel/plugin-transform-literals": "^7.22.5",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.22.5",
+ "@babel/plugin-transform-member-expression-literals": "^7.22.5",
+ "@babel/plugin-transform-modules-amd": "^7.22.5",
+ "@babel/plugin-transform-modules-commonjs": "^7.22.5",
+ "@babel/plugin-transform-modules-systemjs": "^7.22.5",
+ "@babel/plugin-transform-modules-umd": "^7.22.5",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
+ "@babel/plugin-transform-new-target": "^7.22.5",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5",
+ "@babel/plugin-transform-numeric-separator": "^7.22.5",
+ "@babel/plugin-transform-object-rest-spread": "^7.22.5",
+ "@babel/plugin-transform-object-super": "^7.22.5",
+ "@babel/plugin-transform-optional-catch-binding": "^7.22.5",
+ "@babel/plugin-transform-optional-chaining": "^7.22.6",
+ "@babel/plugin-transform-parameters": "^7.22.5",
+ "@babel/plugin-transform-private-methods": "^7.22.5",
+ "@babel/plugin-transform-private-property-in-object": "^7.22.5",
+ "@babel/plugin-transform-property-literals": "^7.22.5",
+ "@babel/plugin-transform-regenerator": "^7.22.5",
+ "@babel/plugin-transform-reserved-words": "^7.22.5",
+ "@babel/plugin-transform-shorthand-properties": "^7.22.5",
+ "@babel/plugin-transform-spread": "^7.22.5",
+ "@babel/plugin-transform-sticky-regex": "^7.22.5",
+ "@babel/plugin-transform-template-literals": "^7.22.5",
+ "@babel/plugin-transform-typeof-symbol": "^7.22.5",
+ "@babel/plugin-transform-unicode-escapes": "^7.22.5",
+ "@babel/plugin-transform-unicode-property-regex": "^7.22.5",
+ "@babel/plugin-transform-unicode-regex": "^7.22.5",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.22.5",
+ "@babel/preset-modules": "^0.1.5",
+ "@babel/types": "^7.22.5",
+ "@nicolo-ribaudo/semver-v6": "^6.3.3",
+ "babel-plugin-polyfill-corejs2": "^0.4.3",
+ "babel-plugin-polyfill-corejs3": "^0.8.1",
+ "babel-plugin-polyfill-regenerator": "^0.5.0",
+ "core-js-compat": "^3.31.0"
},
"dependencies": {
- "@azure/core-tracing": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz",
- "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==",
- "requires": {
- "tslib": "^2.2.0"
- }
- },
- "@tootallnate/once": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
- "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
- },
- "debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "requires": {
- "ms": "2.1.2"
- }
- },
- "http-proxy-agent": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
- "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
- "requires": {
- "@tootallnate/once": "1",
- "agent-base": "6",
- "debug": "4"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ "@nicolo-ribaudo/semver-v6": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
+ "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+ "dev": true
}
}
},
- "@azure/core-tracing": {
- "version": "1.0.0-preview.13",
- "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz",
- "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==",
- "requires": {
- "@opentelemetry/api": "^1.0.1",
- "tslib": "^2.2.0"
- }
- },
- "@azure/core-util": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.0.0.tgz",
- "integrity": "sha512-yWshY9cdPthlebnb3Zuz/j0Lv4kjU6u7PR5sW7A9FF7EX+0irMRJAtyTq5TPiDHJfjH8gTSlnIYFj9m7Ed76IQ==",
+ "@babel/preset-modules": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz",
+ "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==",
+ "dev": true,
"requires": {
- "tslib": "^2.2.0"
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
+ "@babel/plugin-transform-dotall-regex": "^7.4.4",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
}
},
- "@azure/identity": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.0.4.tgz",
- "integrity": "sha512-ZgFubAsmo7dji63NLPaot6O7pmDfceAUPY57uphSCr0hmRj+Cakqb4SUz5SohCHFtscrhcmejRU903Fowz6iXg==",
+ "@babel/register": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.5.tgz",
+ "integrity": "sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ==",
+ "dev": true,
"requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-auth": "^1.3.0",
- "@azure/core-client": "^1.4.0",
- "@azure/core-rest-pipeline": "^1.1.0",
- "@azure/core-tracing": "1.0.0-preview.13",
- "@azure/core-util": "^1.0.0-beta.1",
- "@azure/logger": "^1.0.0",
- "@azure/msal-browser": "^2.16.0",
- "@azure/msal-common": "^4.5.1",
- "@azure/msal-node": "^1.3.0",
- "events": "^3.0.0",
- "jws": "^4.0.0",
- "open": "^8.0.0",
- "stoppable": "^1.1.0",
- "tslib": "^2.2.0",
- "uuid": "^8.3.0"
+ "clone-deep": "^4.0.1",
+ "find-cache-dir": "^2.0.0",
+ "make-dir": "^2.1.0",
+ "pirates": "^4.0.5",
+ "source-map-support": "^0.5.16"
},
"dependencies": {
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
}
}
},
- "@azure/keyvault-keys": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.4.0.tgz",
- "integrity": "sha512-W9sPZebXYa3aar7BGIA+fAsq/sy1nf2TZAETbkv7DRawzVLrWv8QoVVceqNHjy3cigT4HNxXjaPYCI49ez5CUA==",
- "requires": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-http": "^2.0.0",
- "@azure/core-lro": "^2.2.0",
- "@azure/core-paging": "^1.1.1",
- "@azure/core-tracing": "1.0.0-preview.13",
- "@azure/logger": "^1.0.0",
- "tslib": "^2.2.0"
- }
+ "@babel/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==",
+ "dev": true
},
- "@azure/logger": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz",
- "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==",
+ "@babel/runtime": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
+ "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
+ "dev": true,
"requires": {
- "tslib": "^2.2.0"
+ "regenerator-runtime": "^0.13.11"
}
},
- "@azure/msal-browser": {
- "version": "2.24.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.24.0.tgz",
- "integrity": "sha512-P4Z8mQ6hTuA9ss3HCltso7fRmuX1raaU6444G35c0FhaD6hfqViFYRa7hk16AiAs9HkUQHbBaL3gLjKMpX3heA==",
+ "@babel/template": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
+ "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
+ "dev": true,
"requires": {
- "@azure/msal-common": "^6.3.0"
- },
- "dependencies": {
- "@azure/msal-common": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-6.3.0.tgz",
- "integrity": "sha512-ZyLq9GdnLBi/83YpysE86TFKbA0TuvfNAN5Psqu20cdAjLo/4rw4ttiItdh1G//XeGErHk9qn57gi2AYU1b5/Q=="
- }
+ "@babel/code-frame": "^7.22.5",
+ "@babel/parser": "^7.22.5",
+ "@babel/types": "^7.22.5"
}
},
- "@azure/msal-common": {
- "version": "4.5.1",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.5.1.tgz",
- "integrity": "sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ==",
+ "@babel/traverse": {
+ "version": "7.22.6",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz",
+ "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==",
+ "dev": true,
"requires": {
- "debug": "^4.1.1"
+ "@babel/code-frame": "^7.22.5",
+ "@babel/generator": "^7.22.5",
+ "@babel/helper-environment-visitor": "^7.22.5",
+ "@babel/helper-function-name": "^7.22.5",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.22.6",
+ "@babel/types": "^7.22.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
"requires": {
"ms": "2.1.2"
}
},
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
}
}
},
- "@azure/msal-node": {
- "version": "1.14.6",
- "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.14.6.tgz",
- "integrity": "sha512-em/qqFL5tLMxMPl9vormAs13OgZpmQoJbiQ/GlWr+BA77eCLoL+Ehr5xRHowYo+LFe5b+p+PJVkRvT+mLvOkwA==",
+ "@babel/types": {
+ "version": "7.22.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
+ "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
+ "dev": true,
"requires": {
- "@azure/msal-common": "^9.0.2",
- "jsonwebtoken": "^9.0.0",
- "uuid": "^8.3.0"
+ "@babel/helper-string-parser": "^7.22.5",
+ "@babel/helper-validator-identifier": "^7.22.5",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@elastic/transport": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.3.2.tgz",
+ "integrity": "sha512-ZiBYRVPj6pwYW99fueyNU4notDf7ZPs7Ix+4T1btIJsKJmeaORIItIfs+0O7KV4vV+DcvyMhkY1FXQx7kQOODw==",
+ "requires": {
+ "debug": "^4.3.4",
+ "hpagent": "^1.0.0",
+ "ms": "^2.1.3",
+ "secure-json-parse": "^2.4.0",
+ "tslib": "^2.4.0",
+ "undici": "^5.22.1"
},
"dependencies": {
- "@azure/msal-common": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.0.2.tgz",
- "integrity": "sha512-qzwxuF8kZAp+rNUactMCgJh8fblq9D4lSqrrIxMDzLjgSZtjN32ix7r/HBe8QdOr76II9SVVPcMkX4sPzPfQ7w=="
- },
- "jsonwebtoken": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
- "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
- "requires": {
- "jws": "^3.2.2",
- "lodash": "^4.17.21",
- "ms": "^2.1.1",
- "semver": "^7.3.8"
- }
- },
- "jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
- "jwa": "^1.4.1",
- "safe-buffer": "^5.0.1"
+ "ms": "2.1.2"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ }
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
- "requires": {
- "lru-cache": "^6.0.0"
- }
- },
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
+ "@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "@eslint-community/regexpp": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
+ "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
+ "dev": true
+ },
"@eslint/eslintrc": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz",
- "integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
+ "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.3.1",
- "globals": "^13.9.0",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
- "minimatch": "^3.0.4",
+ "minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
@@ -410,6 +1738,12 @@
}
}
},
+ "@eslint/js": {
+ "version": "8.44.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
+ "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
+ "dev": true
+ },
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -417,14 +1751,14 @@
"optional": true
},
"@humanwhocodes/config-array": {
- "version": "0.9.5",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
- "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==",
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
+ "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
- "minimatch": "^3.0.4"
+ "minimatch": "^3.0.5"
},
"dependencies": {
"debug": {
@@ -444,6 +1778,12 @@
}
}
},
+ "@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true
+ },
"@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
@@ -451,9 +1791,10 @@
"dev": true
},
"@jridgewell/gen-mapping": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
- "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+ "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+ "dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@@ -463,40 +1804,40 @@
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
- "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "dev": true
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
- "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "dev": true
},
"@jridgewell/source-map": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
- "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
- "requires": {
- "@jridgewell/gen-mapping": "^0.3.0",
- "@jridgewell/trace-mapping": "^0.3.9"
- }
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.4.tgz",
+ "integrity": "sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
- "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "dev": true
},
"@jridgewell/trace-mapping": {
- "version": "0.3.17",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
- "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "version": "0.3.18",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
+ "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
+ "dev": true,
"requires": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"@js-joda/core": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-4.3.1.tgz",
- "integrity": "sha512-oeaetlodcqVsiZDxnEcqsbs+sXBkASxua0mXs5OXuPQXz3/wdPTMlxwfQ4z2HKcOik3S9voW3QJkp/KLWDhvRQ=="
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz",
+ "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ=="
},
"@jsdevtools/ono": {
"version": "7.1.3",
@@ -532,9 +1873,9 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
},
"@mapbox/node-pre-gyp": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz",
- "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
+ "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==",
"optional": true,
"requires": {
"detect-libc": "^2.0.0",
@@ -548,11 +1889,17 @@
"tar": "^6.1.11"
}
},
+ "@nicolo-ribaudo/chokidar-2": {
+ "version": "2.1.8-no-fsevents.3",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
+ "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
+ "dev": true,
+ "optional": true
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"requires": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -561,14 +1908,12 @@
"@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
},
"@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"requires": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -594,10 +1939,39 @@
"rimraf": "^3.0.2"
}
},
- "@opentelemetry/api": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.1.0.tgz",
- "integrity": "sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ=="
+ "@pkgr/utils": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz",
+ "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "fast-glob": "^3.2.12",
+ "is-glob": "^4.0.3",
+ "open": "^9.1.0",
+ "picocolors": "^1.0.0",
+ "tslib": "^2.5.0"
+ },
+ "dependencies": {
+ "define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true
+ },
+ "open": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+ "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+ "dev": true,
+ "requires": {
+ "default-browser": "^4.0.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^2.2.0"
+ }
+ }
+ }
},
"@redis/bloom": {
"version": "1.2.0",
@@ -634,16 +2008,95 @@
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng=="
},
+ "@rollup/plugin-commonjs": {
+ "version": "25.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.2.tgz",
+ "integrity": "sha512-NGTwaJxIO0klMs+WSFFtBP7b9TdTJ3K76HZkewT8/+yHzMiUGVQgaPtLQxNVYIgT5F7lxkEyVID+yS3K7bhCow==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^5.0.1",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.2",
+ "glob": "^8.0.3",
+ "is-reference": "1.2.1",
+ "magic-string": "^0.27.0"
+ },
+ "dependencies": {
+ "@rollup/pluginutils": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
+ "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
+ "@rollup/plugin-json": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz",
+ "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==",
+ "requires": {
+ "@rollup/pluginutils": "^5.0.1"
+ },
+ "dependencies": {
+ "@rollup/pluginutils": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
+ "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
+ "requires": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ }
+ }
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dev": true,
+ "requires": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ }
+ },
"@rushstack/eslint-patch": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
- "integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz",
+ "integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==",
"dev": true
},
"@sinonjs/commons": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
- "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==",
+ "version": "1.8.6",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
+ "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==",
"dev": true,
"requires": {
"type-detect": "4.0.8"
@@ -659,9 +2112,9 @@
}
},
"@sinonjs/samsam": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz",
- "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==",
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.3.tgz",
+ "integrity": "sha512-nhOb2dWPeb1sd3IQXL/dVPnKHDOAFfvichtBf4xV00/rU1QbPCQqKMbvIheIjqwVjh7qIgf2AHTHi391yMOMpQ==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.6.0",
@@ -670,26 +2123,94 @@
}
},
"@sinonjs/text-encoding": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
- "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz",
+ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
"dev": true
},
"@tediousjs/connection-string": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.3.0.tgz",
- "integrity": "sha512-d/keJiNKfpHo+GmSB8QcsAwBx8h+V1UbdozA5TD+eSLXprNY53JAYub47J9evsSKWDdNG5uVj0FiMozLKuzowQ=="
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.2.tgz",
+ "integrity": "sha512-1R9UC7Qc5wief2oJL+c1+d7v1/oPBayL85u8L/jV2DzIKput1TZ8ZUjj2nxQaSfzu210zp0oFWUrYUiUs8NhBQ=="
},
"@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="
},
- "@types/es-aggregate-error": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz",
- "integrity": "sha512-erqUpFXksaeR2kejKnhnjZjbFxUpGZx4Z7ydNL9ie8tEhXPiZTsLeUDJ6aR1F8j5wWUAtOAQWUqkc7givBJbBA==",
+ "@types/body-parser": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+ "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+ "dev": true,
+ "requires": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/connect": {
+ "version": "3.4.35",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+ "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/cross-spawn": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz",
+ "integrity": "sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/estree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
+ },
+ "@types/express": {
+ "version": "4.17.17",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz",
+ "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==",
+ "dev": true,
+ "requires": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "@types/express-serve-static-core": {
+ "version": "4.17.35",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
+ "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "@types/fs-extra": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
+ "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "@types/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+ "dev": true,
"requires": {
+ "@types/minimatch": "*",
"@types/node": "*"
}
},
@@ -701,17 +2222,36 @@
"@types/unist": "*"
}
},
- "@types/json-schema": {
- "version": "7.0.9",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
- "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
+ "@types/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==",
+ "dev": true
+ },
+ "@types/jquery": {
+ "version": "3.5.16",
+ "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz",
+ "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==",
+ "dev": true,
+ "requires": {
+ "@types/sizzle": "*"
+ }
+ },
+ "@types/js-cookie": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz",
+ "integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==",
"dev": true
},
+ "@types/json-schema": {
+ "version": "7.0.12",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
+ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
+ },
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
- "dev": true
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"@types/lodash": {
"version": "4.14.195",
@@ -731,64 +2271,99 @@
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
- "@types/node": {
- "version": "17.0.31",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz",
- "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q=="
+ "@types/mime": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
+ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
+ "dev": true
},
- "@types/node-fetch": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
- "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
- "requires": {
- "@types/node": "*",
- "form-data": "^3.0.0"
- },
- "dependencies": {
- "form-data": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- }
- }
- }
+ "@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "20.3.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
+ "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw=="
},
"@types/parse5": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
"integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
},
- "@types/tunnel": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
- "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
+ "@types/qs": {
+ "version": "6.9.7",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+ "dev": true
+ },
+ "@types/range-parser": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+ "dev": true
+ },
+ "@types/semver": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
+ "dev": true
+ },
+ "@types/send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
+ "dev": true,
+ "requires": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "@types/serve-static": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz",
+ "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==",
+ "dev": true,
"requires": {
+ "@types/http-errors": "*",
+ "@types/mime": "*",
"@types/node": "*"
}
},
+ "@types/sizzle": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
+ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
+ "dev": true
+ },
+ "@types/underscore": {
+ "version": "1.11.5",
+ "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.5.tgz",
+ "integrity": "sha512-b8e//LrIlhoXaaBcMC0J/s2/lIF9y5VJYKqbW4nA+tW/nqqDk1Dacd1ULLT7zgGsKs7PGbSnqCPzqEniZ0RxYg==",
+ "dev": true
+ },
"@types/unist": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
},
"@typescript-eslint/eslint-plugin": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz",
- "integrity": "sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz",
+ "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.22.0",
- "@typescript-eslint/type-utils": "5.22.0",
- "@typescript-eslint/utils": "5.22.0",
- "debug": "^4.3.2",
- "functional-red-black-tree": "^1.0.1",
- "ignore": "^5.1.8",
- "regexpp": "^3.2.0",
- "semver": "^7.3.5",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.60.1",
+ "@typescript-eslint/type-utils": "5.60.1",
+ "@typescript-eslint/utils": "5.60.1",
+ "debug": "^4.3.4",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"dependencies": {
@@ -810,15 +2385,15 @@
}
},
"@typescript-eslint/parser": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.22.0.tgz",
- "integrity": "sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz",
+ "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.22.0",
- "@typescript-eslint/types": "5.22.0",
- "@typescript-eslint/typescript-estree": "5.22.0",
- "debug": "^4.3.2"
+ "@typescript-eslint/scope-manager": "5.60.1",
+ "@typescript-eslint/types": "5.60.1",
+ "@typescript-eslint/typescript-estree": "5.60.1",
+ "debug": "^4.3.4"
},
"dependencies": {
"debug": {
@@ -839,23 +2414,24 @@
}
},
"@typescript-eslint/scope-manager": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz",
- "integrity": "sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz",
+ "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.22.0",
- "@typescript-eslint/visitor-keys": "5.22.0"
+ "@typescript-eslint/types": "5.60.1",
+ "@typescript-eslint/visitor-keys": "5.60.1"
}
},
"@typescript-eslint/type-utils": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz",
- "integrity": "sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz",
+ "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==",
"dev": true,
"requires": {
- "@typescript-eslint/utils": "5.22.0",
- "debug": "^4.3.2",
+ "@typescript-eslint/typescript-estree": "5.60.1",
+ "@typescript-eslint/utils": "5.60.1",
+ "debug": "^4.3.4",
"tsutils": "^3.21.0"
},
"dependencies": {
@@ -877,23 +2453,23 @@
}
},
"@typescript-eslint/types": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz",
- "integrity": "sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz",
+ "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz",
- "integrity": "sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz",
+ "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.22.0",
- "@typescript-eslint/visitor-keys": "5.22.0",
- "debug": "^4.3.2",
- "globby": "^11.0.4",
+ "@typescript-eslint/types": "5.60.1",
+ "@typescript-eslint/visitor-keys": "5.60.1",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
"is-glob": "^4.0.3",
- "semver": "^7.3.5",
+ "semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"dependencies": {
@@ -915,17 +2491,19 @@
}
},
"@typescript-eslint/utils": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz",
- "integrity": "sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz",
+ "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==",
"dev": true,
"requires": {
+ "@eslint-community/eslint-utils": "^4.2.0",
"@types/json-schema": "^7.0.9",
- "@typescript-eslint/scope-manager": "5.22.0",
- "@typescript-eslint/types": "5.22.0",
- "@typescript-eslint/typescript-estree": "5.22.0",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.60.1",
+ "@typescript-eslint/types": "5.60.1",
+ "@typescript-eslint/typescript-estree": "5.60.1",
"eslint-scope": "^5.1.1",
- "eslint-utils": "^3.0.0"
+ "semver": "^7.3.7"
},
"dependencies": {
"eslint-scope": {
@@ -947,13 +2525,13 @@
}
},
"@typescript-eslint/visitor-keys": {
- "version": "5.22.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz",
- "integrity": "sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==",
+ "version": "5.60.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz",
+ "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.22.0",
- "eslint-visitor-keys": "^3.0.0"
+ "@typescript-eslint/types": "5.60.1",
+ "eslint-visitor-keys": "^3.3.0"
}
},
"@ungap/promise-all-settled": {
@@ -962,16 +2540,20 @@
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
+ "@wessberg/stringutil": {
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/@wessberg/stringutil/-/stringutil-1.0.19.tgz",
+ "integrity": "sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg=="
+ },
"abab": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
- "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "optional": true
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"accepts": {
"version": "1.3.8",
@@ -983,9 +2565,9 @@
}
},
"acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+ "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ=="
},
"acorn-globals": {
"version": "6.0.0",
@@ -1015,14 +2597,14 @@
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
},
"adm-zip": {
- "version": "0.5.9",
- "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
- "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg=="
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
+ "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
- "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
+ "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
},
"agent-base": {
"version": "6.0.2",
@@ -1033,9 +2615,9 @@
},
"dependencies": {
"debug": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
- "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
@@ -1047,6 +2629,34 @@
}
}
},
+ "agentkeepalive": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
+ "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
+ "optional": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "depd": "^2.0.0",
+ "humanize-ms": "^1.2.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "optional": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "optional": true
+ }
+ }
+ },
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -1091,9 +2701,9 @@
}
},
"anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@@ -1114,59 +2724,172 @@
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ }
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
},
"dependencies": {
- "readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "optional": true,
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
"requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
}
},
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "optional": true
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
},
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "optional": true,
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
"requires": {
- "safe-buffer": "~5.2.0"
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
}
}
}
},
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
- },
- "array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
- },
- "array-includes": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz",
- "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.1",
- "get-intrinsic": "^1.1.1",
- "is-string": "^1.0.7"
- }
- },
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -1174,15 +2897,280 @@
"dev": true
},
"array.prototype.flat": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
- "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+ "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
+ }
+ },
+ "array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
"es-shim-unscopables": "^1.0.0"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
}
},
"arraybuffer.slice": {
@@ -1206,32 +3194,85 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
},
"async": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
- "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
},
"aws4": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
- "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
+ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
+ },
+ "axios": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "requires": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "babel-plugin-polyfill-corejs2": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz",
+ "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.17.7",
+ "@babel/helper-define-polyfill-provider": "^0.4.0",
+ "semver": "^6.1.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "babel-plugin-polyfill-corejs3": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz",
+ "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.4.0",
+ "core-js-compat": "^3.30.1"
+ }
+ },
+ "babel-plugin-polyfill-regenerator": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz",
+ "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-define-polyfill-provider": "^0.4.0"
+ }
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
- "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
+ "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
},
"bail": {
"version": "2.0.2",
@@ -1246,7 +3287,7 @@
"base64-arraybuffer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
- "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
+ "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
},
"base64-js": {
"version": "1.5.1",
@@ -1266,11 +3307,17 @@
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"requires": {
"tweetnacl": "^0.14.3"
}
},
+ "big-integer": {
+ "version": "1.6.51",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
+ "dev": true
+ },
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
@@ -1299,12 +3346,12 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -1313,6 +3360,13 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
}
},
"string_decoder": {
@@ -1321,6 +3375,13 @@
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
}
}
}
@@ -1333,7 +3394,7 @@
"bluebird": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
- "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
+ "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ=="
},
"body-parser": {
"version": "1.20.1",
@@ -1354,20 +3415,27 @@
"unpipe": "1.0.0"
}
},
+ "bplist-parser": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
+ "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
+ "dev": true,
+ "requires": {
+ "big-integer": "^1.6.44"
+ }
+ },
"brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^1.0.0"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@@ -1383,6 +3451,18 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
+ "browserslist": {
+ "version": "4.21.9",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
+ "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001503",
+ "electron-to-chromium": "^1.4.431",
+ "node-releases": "^2.0.12",
+ "update-browserslist-db": "^1.0.11"
+ }
+ },
"bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
@@ -1400,7 +3480,7 @@
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"buffer-from": {
"version": "1.1.2",
@@ -1413,14 +3493,31 @@
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"builtins": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-4.1.0.tgz",
- "integrity": "sha512-1bPRZQtmKaO6h7qV1YHXNtr6nCK28k0Zo95KM4dXfILcZZwoHJBN1m3lfLv9LPkcOZlrSr+J1bzMaZFO98Yq0w==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
+ "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
"dev": true,
"requires": {
"semver": "^7.0.0"
}
},
+ "bundle-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
+ "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
+ "dev": true,
+ "requires": {
+ "run-applescript": "^5.0.0"
+ }
+ },
+ "busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "requires": {
+ "streamsearch": "^1.1.0"
+ }
+ },
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1450,6 +3547,17 @@
"ssri": "^8.0.1",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"call-bind": {
@@ -1472,15 +3580,21 @@
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"dev": true
},
+ "caniuse-lite": {
+ "version": "1.0.30001512",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz",
+ "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==",
+ "dev": true
+ },
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
},
"cassandra-driver": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-4.6.3.tgz",
- "integrity": "sha512-npW670TXjTHrdb15LUFN01wssb9vvz6SuNYcppesoKcUXx3Q29nXVhRtnvsnkG0BaSnDGvCCR4udrzYLsbh+sg==",
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/cassandra-driver/-/cassandra-driver-4.6.4.tgz",
+ "integrity": "sha512-SksbIK0cZ2QZRx8ti7w+PnLqldyY+6kU2gRWFChwXFTtrD/ce8cQICDEHxyPwx+DeILwRnMrPf9cjUGizYw9Vg==",
"requires": {
"@types/long": "^4.0.0",
"@types/node": ">=8",
@@ -1545,6 +3659,34 @@
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"optional": true
},
+ "cjstoesm": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/cjstoesm/-/cjstoesm-2.1.2.tgz",
+ "integrity": "sha512-zAbvVcY+HSxy4tTRfskV7DEK7l8nJTjpO9wuQumUu48JWBezqNgt7p9CFe3KACZJtHk8BJECSvl3ccV1qYT2RA==",
+ "requires": {
+ "@wessberg/stringutil": "^1.0.19",
+ "ansi-colors": "^4.1.3",
+ "commander": "^9.3.0",
+ "compatfactory": "^1.0.1",
+ "crosspath": "^2.0.0",
+ "fast-glob": "^3.2.11",
+ "helpertypes": "^0.0.18",
+ "reserved-words": "^0.1.2",
+ "resolve": "^1.22.0"
+ },
+ "dependencies": {
+ "ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="
+ },
+ "commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="
+ }
+ }
+ },
"clean-css": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
@@ -1560,48 +3702,25 @@
"optional": true
},
"cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
+ }
+ },
+ "clone-deep": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
}
},
"cluster-key-slot": {
@@ -1628,6 +3747,12 @@
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"optional": true
},
+ "colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true
+ },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1646,10 +3771,24 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true
+ },
+ "compatfactory": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/compatfactory/-/compatfactory-1.0.1.tgz",
+ "integrity": "sha512-hR9u0HSZTKDNNchPtMHg6myeNx0XO+av7UZIJPsi4rPALJBHi/W5Mbwi19hC/xm6y3JkYpxVYjTqnSGsU5X/iw==",
+ "requires": {
+ "helpertypes": "^0.0.18"
+ }
+ },
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
- "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
+ "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
},
"component-emitter": {
"version": "1.3.0",
@@ -1659,17 +3798,45 @@
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
- "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
+ "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "concurrently": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.0.tgz",
+ "integrity": "sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.2",
+ "date-fns": "^2.30.0",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.8.1",
+ "shell-quote": "^1.8.1",
+ "spawn-command": "0.0.2",
+ "supports-color": "^8.1.1",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.7.2"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"optional": true
},
"content-disposition": {
@@ -1678,19 +3845,18 @@
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"requires": {
"safe-buffer": "5.2.1"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
- }
}
},
"content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
+ },
+ "convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "dev": true
},
"cookie": {
"version": "0.4.1",
@@ -1716,6 +3882,15 @@
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
},
+ "core-js-compat": {
+ "version": "3.31.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz",
+ "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.21.5"
+ }
+ },
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -1731,6 +3906,21 @@
"which": "^2.0.1"
}
},
+ "crosspath": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crosspath/-/crosspath-2.0.0.tgz",
+ "integrity": "sha512-ju88BYCQ2uvjO2bR+SsgLSTwTSctU+6Vp2ePbKPgSCZyy4MWZxYsT738DlKVRE5utUjobjPRm1MkTYKJxCmpTA==",
+ "requires": {
+ "@types/node": "^17.0.36"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "17.0.45",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
+ "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
+ }
+ }
+ },
"cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
@@ -1754,21 +3944,46 @@
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"requires": {
"assert-plus": "^1.0.0"
}
},
"data-urls": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.1.tgz",
- "integrity": "sha512-Ds554NeT5Gennfoo9KN50Vh6tpgtvYEwraYjejXnyTpu1C7oXKxdFk75REooENHE8ndTVOJuv+BEs4/J/xcozw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+ "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
"requires": {
- "abab": "^2.0.3",
+ "abab": "^2.0.6",
"whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^10.0.0"
+ "whatwg-url": "^11.0.0"
+ },
+ "dependencies": {
+ "whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "requires": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ }
+ }
+ }
+ },
+ "date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.21.0"
}
},
+ "date-format": {
+ "version": "4.0.14",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
+ "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg=="
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -1784,14 +3999,37 @@
"dev": true
},
"decimal.js": {
- "version": "10.3.1",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
- "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "default-browser": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
+ "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
+ "dev": true,
+ "requires": {
+ "bundle-name": "^3.0.0",
+ "default-browser-id": "^3.0.0",
+ "execa": "^7.1.1",
+ "titleize": "^3.0.0"
+ }
+ },
+ "default-browser-id": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
+ "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
+ "dev": true,
+ "requires": {
+ "bplist-parser": "^0.2.0",
+ "untildify": "^4.0.0"
+ }
},
"define-lazy-prop": {
"version": "2.0.0",
@@ -1799,23 +4037,22 @@
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="
},
"define-properties": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
- "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
+ "object-keys": "^1.0.12"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"optional": true
},
"denque": {
@@ -1893,7 +4130,7 @@
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
- "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@@ -1913,46 +4150,28 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"ejs": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz",
- "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==",
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
+ "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
"requires": {
"jake": "^10.8.5"
}
},
- "elasticsearch7": {
- "version": "npm:@elastic/elasticsearch@7.17.0",
- "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-7.17.0.tgz",
- "integrity": "sha512-5QLPCjd0uLmLj1lSuKSThjNpq39f6NmlTy9ROLFwG5gjyTgpwSqufDeYG/Fm43Xs05uF7WcscoO7eguI3HuuYA==",
+ "elasticsearch8": {
+ "version": "npm:@elastic/elasticsearch@8.8.1",
+ "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.8.1.tgz",
+ "integrity": "sha512-ibArPKHEmak3jao7xts2gROATiwPQo9aOrWWdix5mJcX1gnjm/UeJBVO901ROmaxFVPKxVnjC9Op3gJYkqagjg==",
"requires": {
- "debug": "^4.3.1",
- "hpagent": "^0.1.1",
- "ms": "^2.1.3",
- "secure-json-parse": "^2.4.0"
- },
- "dependencies": {
- "debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "requires": {
- "ms": "2.1.2"
- },
- "dependencies": {
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- }
- }
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- }
+ "@elastic/transport": "^8.3.2",
+ "tslib": "^2.4.0"
}
},
+ "electron-to-chromium": {
+ "version": "1.4.450",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz",
+ "integrity": "sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==",
+ "dev": true
+ },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -1984,9 +4203,9 @@
}
},
"engine.io": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
- "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.1.tgz",
+ "integrity": "sha512-dfs8EVg/i7QjFsXxn7cCRQ+Wai1G1TlEvHhdYEi80fxn5R1vZ2K661O6v/rezj1FP234SZ14r9CmJke99iYDGg==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
@@ -2017,9 +4236,9 @@
}
},
"engine.io-client": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz",
- "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz",
+ "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==",
"requires": {
"component-emitter": "~1.3.0",
"component-inherit": "0.0.3",
@@ -2061,6 +4280,16 @@
"has-binary2": "~1.0.2"
}
},
+ "enhanced-resolve": {
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ }
+ },
"env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -2074,9 +4303,9 @@
"optional": true
},
"es-abstract": {
- "version": "1.19.5",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.5.tgz",
- "integrity": "sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==",
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
+ "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==",
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
@@ -2084,48 +4313,172 @@
"get-intrinsic": "^1.1.1",
"get-symbol-description": "^1.0.0",
"has": "^1.0.3",
- "has-symbols": "^1.0.3",
+ "has-symbols": "^1.0.2",
"internal-slot": "^1.0.3",
"is-callable": "^1.2.4",
- "is-negative-zero": "^2.0.2",
+ "is-negative-zero": "^2.0.1",
"is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.2",
+ "is-shared-array-buffer": "^1.0.1",
"is-string": "^1.0.7",
- "is-weakref": "^1.0.2",
- "object-inspect": "^1.12.0",
+ "is-weakref": "^1.0.1",
+ "object-inspect": "^1.11.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.1"
- },
- "dependencies": {
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- }
}
},
"es-aggregate-error": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.8.tgz",
- "integrity": "sha512-AKUb5MKLWMozPlFRHOKqWD7yta5uaEhH21qwtnf6FlKjNjTJOoqFi0/G14+FfSkIQhhu6X68Af4xgRC6y8qG4A==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.9.tgz",
+ "integrity": "sha512-fvnX40sb538wdU6r4s35cq4EY6Lr09Upj40BEVem4LEsuW8XgQep9yD5Q1U2KftokNp1rWODFJ2qwZSsAjFpbg==",
"requires": {
"define-properties": "^1.1.4",
- "es-abstract": "^1.19.5",
+ "es-abstract": "^1.20.4",
"function-bind": "^1.1.1",
"functions-have-names": "^1.2.3",
- "get-intrinsic": "^1.1.1",
- "globalthis": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "globalthis": "^1.0.3",
"has-property-descriptors": "^1.0.0"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
+ }
+ },
+ "es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "requires": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
}
},
"es-shim-unscopables": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
"integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
- "dev": true,
"requires": {
"has": "^1.0.3"
}
@@ -2152,64 +4505,67 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
},
"escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true
},
"escodegen": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
- "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"requires": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2",
- "optionator": "^0.8.1",
"source-map": "~0.6.1"
}
},
"eslint": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz",
- "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==",
+ "version": "8.44.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
+ "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
"dev": true,
"requires": {
- "@eslint/eslintrc": "^1.2.2",
- "@humanwhocodes/config-array": "^0.9.2",
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.1.0",
+ "@eslint/js": "8.44.0",
+ "@humanwhocodes/config-array": "^0.11.10",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.1.1",
- "eslint-utils": "^3.0.0",
- "eslint-visitor-keys": "^3.3.0",
- "espree": "^9.3.1",
- "esquery": "^1.4.0",
+ "eslint-scope": "^7.2.0",
+ "eslint-visitor-keys": "^3.4.1",
+ "espree": "^9.6.0",
+ "esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
- "functional-red-black-tree": "^1.0.1",
- "glob-parent": "^6.0.1",
- "globals": "^13.6.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
- "minimatch": "^3.0.4",
+ "minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
- "regexpp": "^3.2.0",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
- "text-table": "^0.2.0",
- "v8-compile-cache": "^2.0.3"
+ "text-table": "^0.2.0"
},
"dependencies": {
"ajv": {
@@ -2224,46 +4580,6 @@
"uri-js": "^4.2.2"
}
},
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2273,117 +4589,49 @@
"ms": "2.1.2"
}
},
- "escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
- "levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- }
- },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
- },
- "optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
- "dev": true,
- "requires": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
- }
- },
- "prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- },
- "type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1"
- }
}
}
},
"eslint-config-etherpad": {
- "version": "3.0.13",
- "resolved": "https://registry.npmjs.org/eslint-config-etherpad/-/eslint-config-etherpad-3.0.13.tgz",
- "integrity": "sha512-Bwt1gDxThlXhY6wan1fb3Jy9kI+yFGctp7+JX6Xs+BwbOdrB4qObgnLKdcLYPKPqv9c4xTSKo3C4BdhTkg7WtQ==",
+ "version": "3.0.15",
+ "resolved": "https://registry.npmjs.org/eslint-config-etherpad/-/eslint-config-etherpad-3.0.15.tgz",
+ "integrity": "sha512-gdhxFlnkcFQhH1fTvJg1xQMmSoNAlYmGOEr8dQQFiO/AWxI7yBlrNVNlHOAyxfHx6c+KeXlLdofZslfL3su45w==",
"dev": true,
"requires": {
- "@rushstack/eslint-patch": "^1.1.3",
- "@typescript-eslint/eslint-plugin": "^5.22.0",
- "@typescript-eslint/parser": "^5.22.0",
- "eslint-import-resolver-typescript": "^2.7.1",
- "eslint-plugin-cypress": "^2.12.1",
+ "@rushstack/eslint-patch": "^1.3.2",
+ "@typescript-eslint/eslint-plugin": "^5.60.1",
+ "@typescript-eslint/parser": "^5.60.1",
+ "eslint-import-resolver-typescript": "^3.5.5",
+ "eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-eslint-comments": "^3.2.0",
- "eslint-plugin-import": "^2.26.0",
- "eslint-plugin-mocha": "^10.0.4",
- "eslint-plugin-n": "^15.2.0",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-mocha": "^10.1.0",
+ "eslint-plugin-n": "^15.7.0",
"eslint-plugin-prefer-arrow": "^1.2.3",
- "eslint-plugin-promise": "^6.0.0",
+ "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0"
}
},
"eslint-import-resolver-node": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
- "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
+ "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
"dev": true,
"requires": {
"debug": "^3.2.7",
- "resolve": "^1.20.0"
+ "is-core-module": "^2.11.0",
+ "resolve": "^1.22.1"
},
"dependencies": {
"debug": {
@@ -2404,16 +4652,19 @@
}
},
"eslint-import-resolver-typescript": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz",
- "integrity": "sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==",
+ "version": "3.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz",
+ "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==",
"dev": true,
"requires": {
"debug": "^4.3.4",
- "glob": "^7.2.0",
+ "enhanced-resolve": "^5.12.0",
+ "eslint-module-utils": "^2.7.4",
+ "get-tsconfig": "^4.5.0",
+ "globby": "^13.1.3",
+ "is-core-module": "^2.11.0",
"is-glob": "^4.0.3",
- "resolve": "^1.22.0",
- "tsconfig-paths": "^3.14.1"
+ "synckit": "^0.8.5"
},
"dependencies": {
"debug": {
@@ -2425,22 +4676,40 @@
"ms": "2.1.2"
}
},
+ "globby": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.1.tgz",
+ "integrity": "sha512-DPCBxctI7dN4EeIqjW2KGqgdcUMbrhJ9AzON+PlxCtvppWhubTLD4+a0GFxiym14ZvacUydTPjLPc2DlKz7EIg==",
+ "dev": true,
+ "requires": {
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.11",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^4.0.0"
+ }
+ },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
+ },
+ "slash": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
+ "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
+ "dev": true
}
}
},
"eslint-module-utils": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz",
- "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
+ "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
"dev": true,
"requires": {
- "debug": "^3.2.7",
- "find-up": "^2.1.0"
+ "debug": "^3.2.7"
},
"dependencies": {
"debug": {
@@ -2461,9 +4730,9 @@
}
},
"eslint-plugin-cypress": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz",
- "integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==",
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.13.3.tgz",
+ "integrity": "sha512-nAPjZE5WopCsgJwl3vHm5iafpV+ZRO76Z9hMyRygWhmg5ODXDPd+9MaPl7kdJ2azj+sO87H3P1PRnggIrz848g==",
"dev": true,
"requires": {
"globals": "^11.12.0"
@@ -2512,29 +4781,48 @@
"requires": {
"escape-string-regexp": "^1.0.5",
"ignore": "^5.0.5"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true
+ }
}
},
"eslint-plugin-import": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
- "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
+ "version": "2.27.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
+ "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
"dev": true,
"requires": {
- "array-includes": "^3.1.4",
- "array.prototype.flat": "^1.2.5",
- "debug": "^2.6.9",
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "array.prototype.flatmap": "^1.3.1",
+ "debug": "^3.2.7",
"doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-module-utils": "^2.7.3",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.7.4",
"has": "^1.0.3",
- "is-core-module": "^2.8.1",
+ "is-core-module": "^2.11.0",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
- "object.values": "^1.1.5",
- "resolve": "^1.22.0",
+ "object.values": "^1.1.6",
+ "resolve": "^1.22.1",
+ "semver": "^6.3.0",
"tsconfig-paths": "^3.14.1"
},
"dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
"doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2543,41 +4831,45 @@
"requires": {
"esutils": "^2.0.2"
}
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
}
}
},
"eslint-plugin-mocha": {
- "version": "10.0.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.0.4.tgz",
- "integrity": "sha512-8wzAeepVY027oBHz/TmBmUr7vhVqoC1KTFeDybFLhbaWKx+aQ7fJJVuUsqcUy+L+G+XvgQBJY+cbAf7hl5DF7Q==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz",
+ "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==",
"dev": true,
"requires": {
"eslint-utils": "^3.0.0",
- "ramda": "^0.28.0"
+ "rambda": "^7.1.0"
}
},
"eslint-plugin-n": {
- "version": "15.2.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.2.0.tgz",
- "integrity": "sha512-lWLg++jGwC88GDGGBX3CMkk0GIWq0y41aH51lavWApOKcMQcYoL3Ayd0lEdtD3SnQtR+3qBvWQS3qGbR2BxRWg==",
+ "version": "15.7.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz",
+ "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==",
"dev": true,
"requires": {
- "builtins": "^4.0.0",
+ "builtins": "^5.0.1",
"eslint-plugin-es": "^4.1.0",
"eslint-utils": "^3.0.0",
"ignore": "^5.1.1",
- "is-core-module": "^2.3.0",
- "minimatch": "^3.0.4",
- "resolve": "^1.10.1",
- "semver": "^6.3.0"
- },
- "dependencies": {
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- }
+ "is-core-module": "^2.11.0",
+ "minimatch": "^3.1.2",
+ "resolve": "^1.22.1",
+ "semver": "^7.3.8"
}
},
"eslint-plugin-prefer-arrow": {
@@ -2587,9 +4879,9 @@
"dev": true
},
"eslint-plugin-promise": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz",
- "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
+ "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
"dev": true
},
"eslint-plugin-you-dont-need-lodash-underscore": {
@@ -2602,9 +4894,9 @@
}
},
"eslint-scope": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
- "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
+ "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
@@ -2629,9 +4921,9 @@
}
},
"eslint-visitor-keys": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
- "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true
},
"esm": {
@@ -2641,14 +4933,14 @@
"optional": true
},
"espree": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
- "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
+ "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
"dev": true,
"requires": {
- "acorn": "^8.7.0",
- "acorn-jsx": "^5.3.1",
- "eslint-visitor-keys": "^3.3.0"
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
}
},
"esprima": {
@@ -2657,9 +4949,9 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
- "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
"dev": true,
"requires": {
"estraverse": "^5.1.0"
@@ -2679,6 +4971,11 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
},
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -2690,9 +4987,9 @@
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
},
"etherpad-cli-client": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/etherpad-cli-client/-/etherpad-cli-client-2.0.1.tgz",
- "integrity": "sha512-cv7ep8NEkrebTIgWS/SBvpt6DhcMKSNu1zZNMFOWdoQkNRn3hVXZU8dedr4Xt5M1zBwPBSBTjisU436/TkEESA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/etherpad-cli-client/-/etherpad-cli-client-2.0.2.tgz",
+ "integrity": "sha512-yPsTD7cfGhnfd4fcrIW7Y8/ru+n93YpriP4Av83O35VYzMQVusgIaRWQ4xj+rbZkd5MBE+nmFaxIGkv0joJSSQ==",
"dev": true,
"requires": {
"async": "^3.2.1",
@@ -2732,12 +5029,6 @@
"util-deprecate": "^1.0.1"
}
},
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
- },
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -2786,6 +5077,73 @@
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
},
+ "execa": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
+ "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.1",
+ "human-signals": "^4.3.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^3.0.7",
+ "strip-final-newline": "^3.0.0"
+ },
+ "dependencies": {
+ "human-signals": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true
+ },
+ "mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+ "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+ "dev": true,
+ "requires": {
+ "path-key": "^4.0.0"
+ }
+ },
+ "onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^4.0.0"
+ }
+ },
+ "path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true
+ },
+ "strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true
+ }
+ }
+ },
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
@@ -2828,11 +5186,6 @@
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
@@ -2854,13 +5207,6 @@
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
- }
}
},
"extend": {
@@ -2871,7 +5217,7 @@
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
},
"fast-deep-equal": {
"version": "3.1.3",
@@ -2879,10 +5225,9 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-glob": {
- "version": "3.2.11",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
- "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
- "dev": true,
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
+ "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -2895,7 +5240,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@@ -2910,7 +5254,8 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
},
"fast-safe-stringify": {
"version": "2.1.1",
@@ -2918,10 +5263,9 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"fastq": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
- "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
- "dev": true,
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"requires": {
"reusify": "^1.0.4"
}
@@ -2936,21 +5280,13 @@
}
},
"filelist": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz",
- "integrity": "sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"requires": {
"minimatch": "^5.0.1"
},
"dependencies": {
- "brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "requires": {
- "balanced-match": "^1.0.0"
- }
- },
"minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@@ -2965,7 +5301,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
@@ -2984,18 +5319,48 @@
"unpipe": "~1.0.0"
}
},
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
"find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
"find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"requires": {
- "locate-path": "^2.0.0"
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
}
},
"flat": {
@@ -3015,20 +5380,27 @@
}
},
"flatted": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
- "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
- "dev": true
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
},
"follow-redirects": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
- "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ=="
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
+ },
+ "for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "requires": {
+ "is-callable": "^1.1.3"
+ }
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
},
"form-data": {
"version": "4.0.0",
@@ -3061,6 +5433,23 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
},
+ "fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "dependencies": {
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ }
+ }
+ },
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
@@ -3068,8 +5457,25 @@
"optional": true,
"requires": {
"minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
+ "dev": true
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3087,11 +5493,16 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
- "functional-red-black-tree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
- "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
- "dev": true
+ "function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ }
},
"functions-have-names": {
"version": "1.2.3",
@@ -3113,23 +5524,6 @@
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "optional": true
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "optional": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
}
},
"generic-pool": {
@@ -3137,6 +5531,12 @@
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="
},
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -3144,15 +5544,22 @@
"dev": true
},
"get-intrinsic": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
- "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
- "has-symbols": "^1.0.1"
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
}
},
+ "get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true
+ },
"get-symbol-description": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
@@ -3162,10 +5569,19 @@
"get-intrinsic": "^1.1.1"
}
},
+ "get-tsconfig": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz",
+ "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==",
+ "dev": true,
+ "requires": {
+ "resolve-pkg-maps": "^1.0.0"
+ }
+ },
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"requires": {
"assert-plus": "^1.0.0"
}
@@ -3193,18 +5609,18 @@
}
},
"globals": {
- "version": "13.13.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz",
- "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==",
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
}
},
"globalthis": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz",
- "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
"requires": {
"define-properties": "^1.1.3"
}
@@ -3223,11 +5639,30 @@
"slash": "^3.0.0"
}
},
+ "gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "requires": {
+ "get-intrinsic": "^1.1.3"
+ }
+ },
"graceful-fs": {
- "version": "4.2.10",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
- "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
- "optional": true
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
},
"growl": {
"version": "1.10.5",
@@ -3238,7 +5673,7 @@
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+ "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
},
"har-validator": {
"version": "5.1.5",
@@ -3276,9 +5711,9 @@
}
},
"has-bigints": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
+ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
},
"has-binary2": {
"version": "1.0.3",
@@ -3291,14 +5726,14 @@
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
- "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+ "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
- "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
+ "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
},
"has-flag": {
"version": "4.0.0",
@@ -3313,10 +5748,15 @@
"get-intrinsic": "^1.1.1"
}
},
+ "has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
+ },
"has-symbols": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
- "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"has-tostringtag": {
"version": "1.0.0",
@@ -3329,7 +5769,7 @@
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"optional": true
},
"hast-util-embedded": {
@@ -3412,15 +5852,20 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
+ "helpertypes": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/helpertypes/-/helpertypes-0.0.18.tgz",
+ "integrity": "sha512-XRhfbSEmR+poXUC5/8AbmYNJb2riOT6qPzjGJZr0S9YedHiaY+/tzPYzWMUclYMEdCYo/1l8PDYrQFCj02v97w=="
+ },
"hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g=="
},
"hpagent": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz",
- "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ=="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz",
+ "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA=="
},
"html-encoding-sniffer": {
"version": "3.0.0",
@@ -3491,9 +5936,9 @@
},
"dependencies": {
"debug": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
- "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
@@ -3508,7 +5953,7 @@
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -3516,18 +5961,18 @@
}
},
"https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"requires": {
"agent-base": "6",
"debug": "4"
},
"dependencies": {
"debug": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
- "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
@@ -3539,15 +5984,36 @@
}
}
},
+ "human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true
+ },
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"optional": true,
"requires": {
"ms": "^2.0.0"
}
},
+ "i18next": {
+ "version": "23.2.7",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.2.7.tgz",
+ "integrity": "sha512-EsbHHvF6b+p+B6Cht5fYWY7VE/WYOrqB1+DNwa1UpLTw6mG5g4tc8KCEjUUOSMUA2yqCsdYQP+PqVq5nBMtOtQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.22.5"
+ }
+ },
+ "i18next-fs-backend": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.5.tgz",
+ "integrity": "sha512-7fgSH8nVhXSBYPHR/W3tEXXhcnwHwNiND4Dfx9knzPzdsWTUTL/TdDVV+DY0dL0asHKLbdoJaXS4LdVW6R8MVQ==",
+ "dev": true
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -3562,9 +6028,15 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
- "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true
+ },
+ "ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true
},
"immediate": {
@@ -3597,7 +6069,7 @@
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
- "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+ "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
},
"infer-owner": {
"version": "1.0.4",
@@ -3640,6 +6112,16 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
+ "is-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ }
+ },
"is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -3677,10 +6159,9 @@
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
},
"is-core-module": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
- "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
- "dev": true,
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
+ "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"requires": {
"has": "^1.0.3"
}
@@ -3701,24 +6182,38 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "dev": true
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "optional": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
+ "is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "requires": {
+ "is-docker": "^3.0.0"
+ },
+ "dependencies": {
+ "is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true
+ }
+ }
+ },
"is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
@@ -3733,13 +6228,12 @@
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-number-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
+ "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==",
"requires": {
"has-tostringtag": "^1.0.0"
}
@@ -3749,11 +6243,26 @@
"resolved": "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz",
"integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw=="
},
+ "is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true
+ },
"is-plain-obj": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz",
"integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw=="
},
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
"is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -3765,6 +6274,15 @@
"integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=",
"dev": true
},
+ "is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -3775,12 +6293,15 @@
}
},
"is-shared-array-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
- "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
- "requires": {
- "call-bind": "^1.0.2"
- }
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz",
+ "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA=="
+ },
+ "is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true
},
"is-string": {
"version": "1.0.7",
@@ -3798,10 +6319,22 @@
"has-symbols": "^1.0.2"
}
},
+ "is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"is-unicode-supported": {
"version": "0.1.0",
@@ -3828,27 +6361,34 @@
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+ "dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "dev": true
+ },
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
},
"jake": {
- "version": "10.8.5",
- "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
- "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
+ "version": "10.8.7",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
+ "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
"requires": {
"async": "^3.2.3",
"chalk": "^4.0.2",
- "filelist": "^1.0.1",
- "minimatch": "^3.0.4"
+ "filelist": "^1.0.4",
+ "minimatch": "^3.1.2"
}
},
"js-cookie": {
@@ -3856,6 +6396,17 @@
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="
},
+ "js-md4": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
+ "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA=="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -3865,14 +6416,14 @@
}
},
"jsbi": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.2.5.tgz",
- "integrity": "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ=="
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz",
+ "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g=="
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
},
"jsdom": {
"version": "19.0.0",
@@ -3908,6 +6459,12 @@
"xml-name-validator": "^4.0.0"
}
},
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
"json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
@@ -3921,13 +6478,13 @@
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"json5": {
"version": "1.0.2",
@@ -3938,6 +6495,14 @@
"minimist": "^1.2.0"
}
},
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"jsonminify": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/jsonminify/-/jsonminify-0.4.2.tgz",
@@ -3955,6 +6520,33 @@
"integrity": "sha1-8K8gBQVPDwrefqIRhhS2ncUS2GU=",
"dev": true
},
+ "jsonwebtoken": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "dependencies": {
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ }
+ }
+ },
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@@ -3999,6 +6591,12 @@
"util-deprecate": "~1.0.1"
}
},
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -4048,9 +6646,15 @@
}
},
"kebab-case": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.1.tgz",
- "integrity": "sha512-txPHx6nVLhv8PHGXIlAk0nYoh894SpAqGPXNvbg2hh8spvHXIah3+vT87DLoa59nKgC6scD3u3xAuRIgiMqbfQ==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz",
+ "integrity": "sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"languages4translatewiki": {
@@ -4059,12 +6663,13 @@
"integrity": "sha1-xDYgbgUtIUkLEQF6RNURj5Ih5ds="
},
"levn": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
"requires": {
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2"
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
}
},
"lie": {
@@ -4077,13 +6682,12 @@
}
},
"locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
+ "p-locate": "^5.0.0"
}
},
"lodash": {
@@ -4096,10 +6700,16 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "dev": true
+ },
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"dev": true
},
"lodash.merge": {
@@ -4115,79 +6725,39 @@
"requires": {
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
}
},
"log4js": {
- "version": "0.6.38",
- "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz",
- "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=",
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz",
+ "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==",
"requires": {
- "readable-stream": "~1.0.2",
- "semver": "~4.3.3"
+ "date-format": "^4.0.14",
+ "debug": "^4.3.4",
+ "flatted": "^3.2.7",
+ "rfdc": "^1.3.0",
+ "streamroller": "^3.1.5"
},
"dependencies": {
- "semver": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
- "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto="
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"long": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz",
- "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8="
+ "integrity": "sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ=="
},
"lru-cache": {
"version": "6.0.0",
@@ -4197,11 +6767,19 @@
"yallist": "^4.0.0"
}
},
+ "magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ }
+ },
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "optional": true,
"requires": {
"semver": "^6.0.0"
},
@@ -4209,8 +6787,7 @@
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "optional": true
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
@@ -4244,17 +6821,6 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"optional": true
},
- "agentkeepalive": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
- "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
- "optional": true,
- "requires": {
- "debug": "^4.1.0",
- "depd": "^2.0.0",
- "humanize-ms": "^1.2.1"
- }
- },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -4275,6 +6841,15 @@
"debug": "4"
}
},
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -4308,22 +6883,26 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
- "dev": true,
"requires": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
@@ -4347,27 +6926,42 @@
"mime-db": "1.52.0"
}
},
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ }
}
},
"minimist": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
- "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true
},
"minipass": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz",
- "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
- "optional": true,
- "requires": {
- "yallist": "^4.0.0"
- }
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "optional": true
},
"minipass-collect": {
"version": "1.0.2",
@@ -4376,6 +6970,17 @@
"optional": true,
"requires": {
"minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"minipass-fetch": {
@@ -4388,6 +6993,17 @@
"minipass": "^3.1.0",
"minipass-sized": "^1.0.3",
"minizlib": "^2.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"minipass-flush": {
@@ -4397,6 +7013,17 @@
"optional": true,
"requires": {
"minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"minipass-pipeline": {
@@ -4406,6 +7033,17 @@
"optional": true,
"requires": {
"minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"minipass-sized": {
@@ -4415,6 +7053,17 @@
"optional": true,
"requires": {
"minipass": "^3.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"minizlib": {
@@ -4425,6 +7074,17 @@
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"mkdirp": {
@@ -4465,6 +7125,27 @@
"yargs-unparser": "2.0.0"
},
"dependencies": {
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
"debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@@ -4482,37 +7163,6 @@
}
}
},
- "escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
- },
- "find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "requires": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "requires": {
- "p-locate": "^5.0.0"
- }
- },
"minimatch": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz",
@@ -4528,36 +7178,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
- "p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "requires": {
- "yocto-queue": "^0.1.0"
- }
- },
- "p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "requires": {
- "p-limit": "^3.0.2"
- }
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true
- },
- "strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true
- },
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -4566,6 +7186,27 @@
"requires": {
"has-flag": "^4.0.0"
}
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
}
}
},
@@ -4584,9 +7225,9 @@
}
},
"mongodb": {
- "version": "3.7.3",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz",
- "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==",
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
+ "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
"requires": {
"bl": "^2.2.1",
"bson": "^1.1.4",
@@ -4602,22 +7243,22 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mssql": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/mssql/-/mssql-8.1.0.tgz",
- "integrity": "sha512-S7j4MoanTCLM09I+wMI9thTS2342mgxCpOQ9kpnFiG3P1NStuQMhPILLOgOt6hwMa/ctfTUKl7eJpB5XGPoe6A==",
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.1.tgz",
+ "integrity": "sha512-m0yTx9xzUtTvJpWJHqknUXUDPRnJXZYOOFNygnNIXn1PBkLsC/rkXQdquObd+M0ZPlBhGC00Jg28zG0wCl7VWg==",
"requires": {
- "@tediousjs/connection-string": "^0.3.0",
- "commander": "^9.1.0",
+ "@tediousjs/connection-string": "^0.4.1",
+ "commander": "^9.4.0",
"debug": "^4.3.3",
"rfdc": "^1.3.0",
"tarn": "^3.0.2",
- "tedious": "^14.0.0"
+ "tedious": "^15.0.1"
},
"dependencies": {
"commander": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
- "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="
},
"debug": {
"version": "4.3.4",
@@ -4648,7 +7289,7 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"readable-stream": {
"version": "2.3.7",
@@ -4664,6 +7305,11 @@
"util-deprecate": "~1.0.1"
}
},
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -4674,6 +7320,16 @@
}
}
},
+ "nano": {
+ "version": "10.1.2",
+ "resolved": "https://registry.npmjs.org/nano/-/nano-10.1.2.tgz",
+ "integrity": "sha512-P3zSoD/sxAgDs/IE9eqpeAXqTdQ/gA9e9dnzaltr4A3WUo/n+eh66T873L+md5v8lXOutX/7dvcHFOO22f5hDw==",
+ "requires": {
+ "axios": "^1.2.2",
+ "node-abort-controller": "^3.0.1",
+ "qs": "^6.11.0"
+ }
+ },
"nanoid": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
@@ -4683,12 +7339,18 @@
"native-duplexpair": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz",
- "integrity": "sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A="
+ "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"negotiator": {
@@ -4697,18 +7359,47 @@
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"nise": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz",
- "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz",
+ "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==",
"dev": true,
"requires": {
- "@sinonjs/commons": "^1.8.3",
- "@sinonjs/fake-timers": ">=5",
+ "@sinonjs/commons": "^2.0.0",
+ "@sinonjs/fake-timers": "^10.0.2",
"@sinonjs/text-encoding": "^0.7.1",
"just-extend": "^4.0.2",
"path-to-regexp": "^1.7.0"
},
"dependencies": {
+ "@sinonjs/commons": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz",
+ "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "requires": {
+ "@sinonjs/commons": "^3.0.0"
+ },
+ "dependencies": {
+ "@sinonjs/commons": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
+ "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ }
+ }
+ },
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
@@ -4721,14 +7412,21 @@
}
},
"node-abort-controller": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz",
- "integrity": "sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw=="
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
+ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
+ },
+ "node-addon-api": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
+ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==",
+ "optional": true
},
"node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
+ "optional": true,
"requires": {
"whatwg-url": "^5.0.0"
},
@@ -4736,17 +7434,20 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "optional": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "optional": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "optional": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -4754,6 +7455,70 @@
}
}
},
+ "node-gyp": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
+ "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==",
+ "optional": true,
+ "requires": {
+ "env-paths": "^2.2.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^9.1.0",
+ "nopt": "^5.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "dependencies": {
+ "are-we-there-yet": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+ "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ }
+ },
+ "gauge": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+ "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ }
+ },
+ "npmlog": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+ "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ }
+ }
+ }
+ },
+ "node-releases": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
+ "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
+ "dev": true
+ },
"nodeify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz",
@@ -4764,6 +7529,62 @@
"promise": "~1.3.0"
}
},
+ "nodemon": {
+ "version": "2.0.22",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
+ "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.5.2",
+ "debug": "^3.2.7",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^5.7.1",
+ "simple-update-notifier": "^1.0.7",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -4780,9 +7601,9 @@
"dev": true
},
"npm": {
- "version": "6.14.16",
- "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.16.tgz",
- "integrity": "sha512-LMiLGYsVNJfVPlQg7v2NYjG7iRIapcLv+oMunlq7fkXVx0BATCjRu7XyWl0G+iuZzHy4CjtM32QB8ox8juTgaw==",
+ "version": "6.14.18",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.18.tgz",
+ "integrity": "sha512-p3SjqSchSuNQUqbJBgwdv0L3O6bKkaSfQrQzJsskNpNKLg0g37c5xTXFV0SqTlX9GWvoGxBELVJMRWq0J8oaLA==",
"requires": {
"JSONStream": "^1.3.5",
"abbrev": "~1.1.1",
@@ -4791,9 +7612,9 @@
"aproba": "^2.0.0",
"archy": "~1.0.0",
"bin-links": "^1.1.8",
- "bluebird": "^3.5.5",
+ "bluebird": "^3.7.2",
"byte-size": "^5.0.1",
- "cacache": "^12.0.3",
+ "cacache": "^12.0.4",
"call-limit": "^1.1.1",
"chownr": "^1.1.4",
"ci-info": "^2.0.0",
@@ -4801,19 +7622,19 @@
"cli-table3": "^0.5.1",
"cmd-shim": "^3.0.3",
"columnify": "~1.5.4",
- "config-chain": "^1.1.12",
+ "config-chain": "^1.1.13",
"debuglog": "*",
"detect-indent": "~5.0.0",
"detect-newline": "^2.1.0",
- "dezalgo": "~1.0.3",
+ "dezalgo": "^1.0.4",
"editor": "~1.0.0",
- "figgy-pudding": "^3.5.1",
+ "figgy-pudding": "^3.5.2",
"find-npm-prefix": "^1.0.2",
"fs-vacuum": "~1.2.10",
"fs-write-stream-atomic": "~1.0.10",
"gentle-fs": "^2.3.1",
- "glob": "^7.1.6",
- "graceful-fs": "^4.2.4",
+ "glob": "^7.2.3",
+ "graceful-fs": "^4.2.10",
"has-unicode": "~2.0.1",
"hosted-git-info": "^2.8.9",
"iferr": "^1.0.2",
@@ -4823,7 +7644,7 @@
"inherits": "^2.0.4",
"ini": "^1.3.8",
"init-package-json": "^1.10.3",
- "is-cidr": "^3.0.0",
+ "is-cidr": "^3.1.1",
"json-parse-better-errors": "^1.0.2",
"lazy-property": "~1.0.0",
"libcipm": "^4.0.8",
@@ -4834,7 +7655,7 @@
"libnpmsearch": "^2.0.2",
"libnpmteam": "^1.0.2",
"libnpx": "^10.2.4",
- "lock-verify": "^2.1.0",
+ "lock-verify": "^2.2.2",
"lockfile": "^1.0.4",
"lodash._baseindexof": "*",
"lodash._baseuniq": "~4.6.0",
@@ -4848,11 +7669,11 @@
"lodash.uniq": "~4.5.0",
"lodash.without": "~4.4.0",
"lru-cache": "^5.1.1",
- "meant": "^1.0.2",
+ "meant": "^1.0.3",
"mississippi": "^3.0.0",
- "mkdirp": "^0.5.5",
+ "mkdirp": "^0.5.6",
"move-concurrently": "^1.0.1",
- "node-gyp": "^5.1.0",
+ "node-gyp": "^5.1.1",
"nopt": "^4.0.3",
"normalize-package-data": "^2.5.0",
"npm-audit-report": "^1.3.3",
@@ -4873,19 +7694,19 @@
"path-is-inside": "~1.0.2",
"promise-inflight": "~1.0.1",
"qrcode-terminal": "^0.12.0",
- "query-string": "^6.8.2",
- "qw": "~1.0.1",
+ "query-string": "^6.14.1",
+ "qw": "^1.0.2",
"read": "~1.0.7",
"read-cmd-shim": "^1.0.5",
"read-installed": "~4.0.3",
- "read-package-json": "^2.1.1",
+ "read-package-json": "^2.1.2",
"read-package-tree": "^5.3.1",
"readable-stream": "^3.6.0",
"readdir-scoped-modules": "^1.1.0",
- "request": "^2.88.0",
+ "request": "^2.88.2",
"retry": "^0.12.0",
"rimraf": "^2.7.1",
- "safe-buffer": "^5.1.2",
+ "safe-buffer": "^5.2.1",
"semver": "^5.7.1",
"sha": "^3.0.0",
"slide": "~1.1.6",
@@ -4901,7 +7722,7 @@
"unique-filename": "^1.1.1",
"unpipe": "~1.0.0",
"update-notifier": "^2.5.0",
- "uuid": "^3.3.3",
+ "uuid": "^3.4.0",
"validate-npm-package-license": "^3.0.4",
"validate-npm-package-name": "~3.0.0",
"which": "^1.3.1",
@@ -4909,6 +7730,14 @@
"write-file-atomic": "^2.4.3"
},
"dependencies": {
+ "@iarna/cli": {
+ "version": "2.1.0",
+ "bundled": true,
+ "requires": {
+ "glob": "^7.1.2",
+ "signal-exit": "^3.0.2"
+ }
+ },
"JSONStream": {
"version": "1.3.5",
"bundled": true,
@@ -4988,6 +7817,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -4995,6 +7830,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -5004,7 +7845,7 @@
"bundled": true
},
"asn1": {
- "version": "0.2.4",
+ "version": "0.2.6",
"bundled": true,
"requires": {
"safer-buffer": "~2.1.0"
@@ -5023,17 +7864,16 @@
"bundled": true
},
"aws4": {
- "version": "1.8.0",
+ "version": "1.11.0",
"bundled": true
},
"balanced-match": {
- "version": "1.0.0",
+ "version": "1.0.2",
"bundled": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"bundled": true,
- "optional": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@@ -5051,7 +7891,7 @@
}
},
"bluebird": {
- "version": "3.5.5",
+ "version": "3.7.2",
"bundled": true
},
"boxen": {
@@ -5092,7 +7932,7 @@
"bundled": true
},
"cacache": {
- "version": "12.0.3",
+ "version": "12.0.4",
"bundled": true,
"requires": {
"bluebird": "^3.5.5",
@@ -5183,7 +8023,7 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
+ "version": "4.1.1",
"bundled": true
},
"is-fullwidth-code-point": {
@@ -5249,7 +8089,7 @@
}
},
"combined-stream": {
- "version": "1.0.6",
+ "version": "1.0.8",
"bundled": true,
"requires": {
"delayed-stream": "~1.0.0"
@@ -5280,6 +8120,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -5287,12 +8133,18 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
},
"config-chain": {
- "version": "1.1.12",
+ "version": "1.1.13",
"bundled": true,
"requires": {
"ini": "^1.3.4",
@@ -5408,7 +8260,7 @@
"bundled": true
},
"decode-uri-component": {
- "version": "0.2.0",
+ "version": "0.2.2",
"bundled": true
},
"deep-extend": {
@@ -5446,7 +8298,7 @@
"bundled": true
},
"dezalgo": {
- "version": "1.0.3",
+ "version": "1.0.4",
"bundled": true,
"requires": {
"asap": "^2.0.0",
@@ -5489,6 +8341,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -5496,6 +8354,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -5503,7 +8367,6 @@
"ecc-jsbn": {
"version": "0.1.2",
"bundled": true,
- "optional": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@@ -5532,7 +8395,7 @@
}
},
"env-paths": {
- "version": "2.2.0",
+ "version": "2.2.1",
"bundled": true
},
"err-code": {
@@ -5613,7 +8476,11 @@
"bundled": true
},
"figgy-pudding": {
- "version": "3.5.1",
+ "version": "3.5.2",
+ "bundled": true
+ },
+ "filter-obj": {
+ "version": "1.1.0",
"bundled": true
},
"find-npm-prefix": {
@@ -5639,6 +8506,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -5646,6 +8519,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -5655,11 +8534,11 @@
"bundled": true
},
"form-data": {
- "version": "2.3.2",
+ "version": "2.3.3",
"bundled": true,
"requires": {
"asynckit": "^0.4.0",
- "combined-stream": "1.0.6",
+ "combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
@@ -5682,6 +8561,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -5689,6 +8574,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -5744,6 +8635,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -5751,6 +8648,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -5842,15 +8745,24 @@
}
},
"glob": {
- "version": "7.1.6",
+ "version": "7.2.3",
"bundled": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.0.4",
+ "minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
+ },
+ "dependencies": {
+ "minimatch": {
+ "version": "3.1.2",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
}
},
"global-dirs": {
@@ -5884,7 +8796,7 @@
}
},
"graceful-fs": {
- "version": "4.2.4",
+ "version": "4.2.10",
"bundled": true
},
"har-schema": {
@@ -6064,7 +8976,7 @@
}
},
"is-cidr": {
- "version": "3.0.0",
+ "version": "3.1.1",
"bundled": true,
"requires": {
"cidr-regex": "^2.0.10"
@@ -6148,13 +9060,16 @@
},
"jsbn": {
"version": "0.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"bundled": true
},
+ "json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "bundled": true
+ },
"json-schema": {
"version": "0.4.0",
"bundled": true
@@ -6358,9 +9273,10 @@
}
},
"lock-verify": {
- "version": "2.1.0",
+ "version": "2.2.2",
"bundled": true,
"requires": {
+ "@iarna/cli": "^2.1.0",
"npm-package-arg": "^6.1.0",
"semver": "^5.4.1"
}
@@ -6467,7 +9383,7 @@
}
},
"meant": {
- "version": "1.0.2",
+ "version": "1.0.3",
"bundled": true
},
"mime-db": {
@@ -6481,6 +9397,17 @@
"mime-db": "~1.35.0"
}
},
+ "minimatch": {
+ "version": "3.1.2",
+ "bundled": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.6",
+ "bundled": true
+ },
"minizlib": {
"version": "1.3.3",
"bundled": true,
@@ -6515,10 +9442,10 @@
}
},
"mkdirp": {
- "version": "0.5.5",
+ "version": "0.5.6",
"bundled": true,
"requires": {
- "minimist": "^1.2.5"
+ "minimist": "^1.2.6"
}
},
"move-concurrently": {
@@ -6557,7 +9484,7 @@
}
},
"node-gyp": {
- "version": "5.1.0",
+ "version": "5.1.1",
"bundled": true,
"requires": {
"env-paths": "^2.2.0",
@@ -6856,6 +9783,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -6863,6 +9796,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -6948,7 +9887,7 @@
"bundled": true
},
"psl": {
- "version": "1.1.29",
+ "version": "1.9.0",
"bundled": true
},
"pump": {
@@ -6978,30 +9917,26 @@
}
}
},
- "punycode": {
- "version": "1.4.1",
- "bundled": true
- },
"qrcode-terminal": {
"version": "0.12.0",
"bundled": true
},
"qs": {
"version": "6.5.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
- "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
+ "bundled": true
},
"query-string": {
- "version": "6.8.2",
+ "version": "6.14.1",
"bundled": true,
"requires": {
"decode-uri-component": "^0.2.0",
+ "filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
"qw": {
- "version": "1.0.1",
+ "version": "1.0.2",
"bundled": true
},
"rc": {
@@ -7042,12 +9977,11 @@
}
},
"read-package-json": {
- "version": "2.1.1",
+ "version": "2.1.2",
"bundled": true,
"requires": {
"glob": "^7.1.1",
- "graceful-fs": "^4.1.2",
- "json-parse-better-errors": "^1.0.1",
+ "json-parse-even-better-errors": "^2.3.0",
"normalize-package-data": "^2.0.0",
"npm-normalize-package-bin": "^1.0.0"
}
@@ -7096,7 +10030,7 @@
}
},
"request": {
- "version": "2.88.0",
+ "version": "2.88.2",
"bundled": true,
"requires": {
"aws-sign2": "~0.7.0",
@@ -7106,7 +10040,7 @@
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
- "har-validator": "~5.1.0",
+ "har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
@@ -7116,7 +10050,7 @@
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
- "tough-cookie": "~2.4.3",
+ "tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
@@ -7158,7 +10092,7 @@
}
},
"safe-buffer": {
- "version": "5.1.2",
+ "version": "5.2.1",
"bundled": true
},
"safer-buffer": {
@@ -7304,7 +10238,7 @@
"bundled": true
},
"sshpk": {
- "version": "1.14.2",
+ "version": "1.17.0",
"bundled": true,
"requires": {
"asn1": "~0.2.3",
@@ -7352,6 +10286,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -7359,6 +10299,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -7500,6 +10446,12 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
},
"string_decoder": {
@@ -7507,6 +10459,12 @@
"bundled": true,
"requires": {
"safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true
+ }
}
}
}
@@ -7520,11 +10478,17 @@
"bundled": true
},
"tough-cookie": {
- "version": "2.4.3",
+ "version": "2.5.0",
"bundled": true,
"requires": {
- "psl": "^1.1.24",
- "punycode": "^1.4.1"
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "bundled": true
+ }
}
},
"tunnel-agent": {
@@ -7536,8 +10500,7 @@
},
"tweetnacl": {
"version": "0.14.5",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"typedarray": {
"version": "0.0.6",
@@ -7597,7 +10560,7 @@
}
},
"uri-js": {
- "version": "4.4.0",
+ "version": "4.4.1",
"bundled": true,
"requires": {
"punycode": "^2.1.0"
@@ -7632,7 +10595,7 @@
}
},
"uuid": {
- "version": "3.3.3",
+ "version": "3.4.0",
"bundled": true
},
"validate-npm-package-license": {
@@ -7719,7 +10682,7 @@
},
"dependencies": {
"ansi-regex": {
- "version": "4.1.0",
+ "version": "4.1.1",
"bundled": true
},
"is-fullwidth-code-point": {
@@ -7865,6 +10828,15 @@
}
}
},
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
"npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
@@ -7878,9 +10850,9 @@
}
},
"nwsapi": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
- "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ=="
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz",
+ "integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ=="
},
"oauth-sign": {
"version": "0.9.0",
@@ -7890,13 +10862,13 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"optional": true
},
"object-inspect": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
- "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
},
"object-keys": {
"version": "1.1.1",
@@ -7915,14 +10887,146 @@
}
},
"object.values": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz",
- "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==",
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.1"
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
}
},
"observable-fns": {
@@ -7946,15 +11050,24 @@
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"requires": {
"wrappy": "1"
}
},
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
"open": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
- "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"requires": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
@@ -8026,34 +11139,35 @@
}
},
"optionator": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
- "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
"requires": {
- "deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.6",
- "levn": "~0.3.0",
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2",
- "word-wrap": "~1.2.3"
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
}
},
"p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
- "p-try": "^1.0.0"
+ "yocto-queue": "^0.1.0"
}
},
"p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"requires": {
- "p-limit": "^1.1.0"
+ "p-limit": "^3.0.2"
}
},
"p-map": {
@@ -8066,9 +11180,9 @@
}
},
"p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"packet-reader": {
@@ -8112,9 +11226,9 @@
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"path-is-absolute": {
@@ -8146,26 +11260,33 @@
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
"pg": {
- "version": "8.7.3",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
- "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==",
+ "version": "8.11.1",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.1.tgz",
+ "integrity": "sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
- "pg-connection-string": "^2.5.0",
- "pg-pool": "^3.5.1",
- "pg-protocol": "^1.5.0",
+ "pg-cloudflare": "^1.1.1",
+ "pg-connection-string": "^2.6.1",
+ "pg-pool": "^3.6.1",
+ "pg-protocol": "^1.6.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
+ "pg-cloudflare": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
+ "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
+ "optional": true
+ },
"pg-connection-string": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
- "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
+ "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg=="
},
"pg-int8": {
"version": "1.0.1",
@@ -8173,14 +11294,14 @@
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz",
- "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ=="
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
+ "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og=="
},
"pg-protocol": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
- "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+ "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
},
"pg-types": {
"version": "2.2.0",
@@ -8202,12 +11323,83 @@
"split2": "^4.1.0"
}
},
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
+ "pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true
+ }
+ }
+ },
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@@ -8216,7 +11408,7 @@
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
- "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
},
"postgres-date": {
"version": "1.0.7",
@@ -8232,14 +11424,10 @@
}
},
"prelude-ls": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
- },
- "process": {
- "version": "0.11.10",
- "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
- "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
},
"process-nextick-args": {
"version": "2.0.1",
@@ -8291,14 +11479,20 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"psl": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
- "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
+ },
+ "pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true
},
"punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
},
"qs": {
"version": "6.11.0",
@@ -8308,16 +11502,20 @@
"side-channel": "^1.0.4"
}
},
+ "querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+ },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
- "ramda": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz",
- "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==",
+ "rambda": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz",
+ "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==",
"dev": true
},
"random-bytes": {
@@ -8356,14 +11554,14 @@
}
},
"readable-stream": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
- "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "optional": true,
"requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.1",
- "isarray": "0.0.1",
- "string_decoder": "~0.10.x"
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
}
},
"readdirp": {
@@ -8375,12 +11573,107 @@
"picomatch": "^2.2.1"
}
},
+ "redis": {
+ "version": "4.6.7",
+ "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz",
+ "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==",
+ "requires": {
+ "@redis/bloom": "1.2.0",
+ "@redis/client": "1.5.8",
+ "@redis/graph": "1.1.0",
+ "@redis/json": "1.0.4",
+ "@redis/search": "1.1.3",
+ "@redis/time-series": "1.0.4"
+ }
+ },
+ "regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "dev": true
+ },
+ "regenerate-unicode-properties": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz",
+ "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz",
+ "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.4"
+ }
+ },
+ "regexp.prototype.flags": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
+ "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "functions-have-names": "^1.2.3"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ }
+ }
+ },
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
+ "regexpu-core": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
+ "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==",
+ "dev": true,
+ "requires": {
+ "@babel/regjsgen": "^0.8.0",
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.1.0",
+ "regjsparser": "^0.9.1",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.1.0"
+ }
+ },
+ "regjsparser": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz",
+ "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
+ "dev": true
+ }
+ }
+ },
"rehype": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.1.tgz",
@@ -8487,7 +11780,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
},
"require-from-string": {
@@ -8495,78 +11788,265 @@
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
- "resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+ },
+ "reserved-words": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz",
+ "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw=="
+ },
+ "resolve": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "requires": {
+ "is-core-module": "^2.11.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true
+ },
+ "rethinkdb": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/rethinkdb/-/rethinkdb-2.4.2.tgz",
+ "integrity": "sha512-6DzwqEpFc8cqesAdo07a845oBRxLiHvWzopTKBo/uY2ypGWIsJQFJk3wjRDtSEhczxJqLS0jnf37rwgzYAw8NQ==",
+ "requires": {
+ "bluebird": ">= 2.3.2 < 3"
+ }
+ },
+ "retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "optional": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
+ },
+ "rfdc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "rollup-plugin-copy": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz",
+ "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==",
+ "dev": true,
+ "requires": {
+ "@types/fs-extra": "^8.0.1",
+ "colorette": "^1.1.0",
+ "fs-extra": "^8.1.0",
+ "globby": "10.0.1",
+ "is-plain-object": "^3.0.0"
+ },
+ "dependencies": {
+ "globby": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
+ "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
+ "dev": true,
+ "requires": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ }
+ },
+ "is-plain-object": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
+ "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
+ "dev": true
+ }
+ }
+ },
+ "rollup-plugin-typescript2": {
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.35.0.tgz",
+ "integrity": "sha512-szcIO9hPUx3PhQl91u4pfNAH2EKbtrXaES+m163xQVE5O1CC0ea6YZV/5woiDDW3CR9jF2CszPrKN+AFiND0bg==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^4.1.2",
+ "find-cache-dir": "^3.3.2",
+ "fs-extra": "^10.0.0",
+ "semver": "^7.3.7",
+ "tslib": "^2.4.0"
+ },
+ "dependencies": {
+ "find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ }
+ },
+ "find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
+ "jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^4.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.2.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "requires": {
+ "find-up": "^4.0.0"
+ }
+ },
+ "universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true
+ }
+ }
+ },
+ "run-applescript": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
+ "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
+ "dev": true,
"requires": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
+ "execa": "^5.0.0"
},
"dependencies": {
- "is-core-module": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
- "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
+ "execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
"requires": {
- "has": "^1.0.3"
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
}
}
}
},
- "resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true
- },
- "rethinkdb": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/rethinkdb/-/rethinkdb-2.4.2.tgz",
- "integrity": "sha512-6DzwqEpFc8cqesAdo07a845oBRxLiHvWzopTKBo/uY2ypGWIsJQFJk3wjRDtSEhczxJqLS0jnf37rwgzYAw8NQ==",
- "requires": {
- "bluebird": ">= 2.3.2 < 3"
- }
- },
- "retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
- "optional": true
- },
- "reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true
- },
- "rfdc": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
- "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "requires": {
- "glob": "^7.1.3"
- }
- },
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"requires": {
"queue-microtask": "^1.2.2"
}
},
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
"safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ }
},
"safer-buffer": {
"version": "2.1.2",
@@ -8582,11 +12062,6 @@
"sparse-bitfield": "^3.0.3"
}
},
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
- },
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@@ -8614,20 +12089,12 @@
"jszip": "^3.10.1",
"tmp": "^0.2.1",
"ws": ">=8.13.0"
- },
- "dependencies": {
- "ws": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
- "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
- "dev": true
- }
}
},
"semver": {
- "version": "7.5.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
- "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+ "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"requires": {
"lru-cache": "^6.0.0"
}
@@ -8682,13 +12149,13 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"optional": true
},
"set-cookie-parser": {
- "version": "2.4.8",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz",
- "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
+ "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
"dev": true
},
"setimmediate": {
@@ -8702,6 +12169,15 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "shallow-clone": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.2"
+ }
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8715,6 +12191,12 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
+ "shell-quote": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
+ "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
+ "dev": true
+ },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -8728,13 +12210,12 @@
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "optional": true
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-git": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.16.0.tgz",
- "integrity": "sha512-zuWYsOLEhbJRWVxpjdiXl6eyAyGo/KzVW+KFhhw9MqEEJttcq+32jTWSGyxTdf9e/YCohxRE+9xpWFj9FdiJNw==",
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.1.tgz",
+ "integrity": "sha512-Ck+rcjVaE1HotraRAS8u/+xgTvToTuoMkT9/l9lvuP5jftwnYUp6DwuJzsKErHgfyRk8IB8pqGHWEbM3tLgV1w==",
"requires": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
@@ -8756,6 +12237,23 @@
}
}
},
+ "simple-update-notifier": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
+ "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
+ "dev": true,
+ "requires": {
+ "semver": "~7.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+ "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+ "dev": true
+ }
+ }
+ },
"sinon": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.2.tgz",
@@ -8768,23 +12266,6 @@
"diff": "^5.0.0",
"nise": "^5.1.1",
"supports-color": "^7.2.0"
- },
- "dependencies": {
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
}
},
"slash": {
@@ -8800,15 +12281,15 @@
"optional": true
},
"socket.io": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
- "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz",
+ "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==",
"requires": {
"debug": "~4.1.0",
- "engine.io": "~3.5.0",
+ "engine.io": "~3.6.0",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
- "socket.io-client": "2.4.0",
+ "socket.io-client": "2.5.0",
"socket.io-parser": "~3.4.0"
},
"dependencies": {
@@ -8833,9 +12314,9 @@
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
},
"socket.io-client": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
- "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz",
+ "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==",
"requires": {
"backo2": "1.0.2",
"component-bind": "1.0.0",
@@ -8861,7 +12342,7 @@
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
- "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+ "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
},
"socket.io-parser": {
"version": "3.3.3",
@@ -8970,12 +12451,18 @@
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"optional": true,
"requires": {
"memory-pager": "^1.0.2"
}
},
+ "spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
"split-grid": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz",
@@ -8983,19 +12470,31 @@
"dev": true
},
"split2": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
- "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ=="
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
},
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
+ "sqlite3": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz",
+ "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==",
+ "optional": true,
+ "requires": {
+ "@mapbox/node-pre-gyp": "^1.0.0",
+ "node-addon-api": "^4.2.0",
+ "node-gyp": "8.x",
+ "tar": "^6.1.11"
+ }
+ },
"sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
- "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
+ "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="
},
"sshpk": {
"version": "1.17.0",
@@ -9020,6 +12519,17 @@
"optional": true,
"requires": {
"minipass": "^3.1.1"
+ },
+ "dependencies": {
+ "minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "optional": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ }
}
},
"statuses": {
@@ -9032,11 +12542,40 @@
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="
},
+ "streamroller": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
+ "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==",
+ "requires": {
+ "date-format": "^4.0.14",
+ "debug": "^4.3.4",
+ "fs-extra": "^8.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ }
+ }
+ },
+ "streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
+ },
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "optional": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -9046,20 +12585,150 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "optional": true
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "optional": true,
"requires": {
"ansi-regex": "^5.0.1"
}
}
}
},
+ "string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "dependencies": {
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ }
+ }
+ },
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
@@ -9079,9 +12748,13 @@
}
},
"string_decoder": {
- "version": "0.10.31",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
},
"stringify-entities": {
"version": "4.0.2",
@@ -9092,10 +12765,30 @@
"character-entities-legacy": "^3.0.0"
}
},
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ }
+ }
+ },
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
+ },
+ "strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true
},
"strip-json-comments": {
@@ -9129,26 +12822,6 @@
"ms": "2.1.2"
}
},
- "dezalgo": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
- "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
- "requires": {
- "asap": "^2.0.0",
- "wrappy": "1"
- }
- },
- "formidable": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
- "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
- "requires": {
- "dezalgo": "^1.0.4",
- "hexoid": "^1.0.0",
- "once": "^1.4.0",
- "qs": "^6.11.0"
- }
- },
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -9158,22 +12831,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "qs": {
- "version": "6.11.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
- "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
- "requires": {
- "side-channel": "^1.0.4"
- }
- },
- "semver": {
- "version": "7.5.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
- "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
- "requires": {
- "lru-cache": "^6.0.0"
- }
}
}
},
@@ -9211,15 +12868,31 @@
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
},
+ "synckit": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+ "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+ "dev": true,
+ "requires": {
+ "@pkgr/utils": "^2.3.1",
+ "tslib": "^2.5.0"
+ }
+ },
+ "tapable": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "dev": true
+ },
"tar": {
- "version": "6.1.11",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
- "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
+ "version": "6.1.15",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
+ "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
"optional": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
- "minipass": "^3.0.0",
+ "minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
@@ -9231,28 +12904,28 @@
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="
},
"tedious": {
- "version": "14.5.0",
- "resolved": "https://registry.npmjs.org/tedious/-/tedious-14.5.0.tgz",
- "integrity": "sha512-Mr/ku6J0yku9MvWKO7e//awwI52122jS5AYRz/VOI2jZZawv84iHPKF/FnHBoIEKlRjzahrtevfpNktw/eBAEw==",
- "requires": {
- "@azure/identity": "^2.0.1",
- "@azure/keyvault-keys": "^4.3.0",
- "@js-joda/core": "^4.0.0",
- "@types/es-aggregate-error": "^1.0.2",
+ "version": "15.1.3",
+ "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz",
+ "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==",
+ "requires": {
+ "@azure/identity": "^2.0.4",
+ "@azure/keyvault-keys": "^4.4.0",
+ "@js-joda/core": "^5.2.0",
"bl": "^5.0.0",
- "es-aggregate-error": "^1.0.7",
+ "es-aggregate-error": "^1.0.8",
"iconv-lite": "^0.6.3",
- "jsbi": "^3.2.1",
+ "js-md4": "^0.3.2",
+ "jsbi": "^4.3.0",
"native-duplexpair": "^1.0.0",
- "node-abort-controller": "^3.0.0",
+ "node-abort-controller": "^3.0.1",
"punycode": "^2.1.0",
"sprintf-js": "^1.1.2"
},
"dependencies": {
"bl": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz",
- "integrity": "sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
+ "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
"requires": {
"buffer": "^6.0.3",
"inherits": "^2.0.4",
@@ -9273,20 +12946,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
- },
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -9298,12 +12966,12 @@
}
},
"terser": {
- "version": "5.16.5",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz",
- "integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==",
+ "version": "5.18.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz",
+ "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==",
"requires": {
- "@jridgewell/source-map": "^0.3.2",
- "acorn": "^8.5.0",
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
}
@@ -9311,7 +12979,7 @@
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
"threads": {
@@ -9355,6 +13023,12 @@
"resolved": "https://registry.npmjs.org/tinycon/-/tinycon-0.6.8.tgz",
"integrity": "sha1-59oiPj7gy/nbeWP6M1aZuyF3enM="
},
+ "titleize": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
+ "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
+ "dev": true
+ },
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -9367,25 +13041,51 @@
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
- "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
+ "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
+ "touch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+ "dev": true,
+ "requires": {
+ "nopt": "~1.0.10"
+ },
+ "dependencies": {
+ "nopt": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+ "dev": true,
+ "requires": {
+ "abbrev": "1"
+ }
+ }
+ }
+ },
"tough-cookie": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
- "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
- "universalify": "^0.1.2"
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
}
},
"tr46": {
@@ -9396,27 +13096,33 @@
"punycode": "^2.1.1"
}
},
+ "tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true
+ },
"trough": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/trough/-/trough-2.0.2.tgz",
"integrity": "sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w=="
},
"tsconfig-paths": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
- "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
+ "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==",
"dev": true,
"requires": {
"@types/json5": "^0.0.29",
- "json5": "^1.0.1",
+ "json5": "^1.0.2",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
}
},
"tslib": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
+ "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
},
"tsutils": {
"version": "3.21.0",
@@ -9435,15 +13141,10 @@
}
}
},
- "tunnel": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
- "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
- },
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -9451,14 +13152,15 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
},
"type-check": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
"requires": {
- "prelude-ls": "~1.1.2"
+ "prelude-ls": "^1.2.1"
}
},
"type-detect": {
@@ -9482,6 +13184,16 @@
"mime-types": "~2.1.24"
}
},
+ "typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ }
+ },
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
@@ -9489,171 +13201,270 @@
"dev": true
},
"ueberdb2": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/ueberdb2/-/ueberdb2-4.0.1.tgz",
- "integrity": "sha512-wo377quP2M/g3j7LoTMMVVX7ED3gj1sk8bkFR1plNEh/TLEgumdjfJl5KmpkXfIj2j6VYmQY4yEhwszpyRRRFg==",
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/ueberdb2/-/ueberdb2-4.1.5.tgz",
+ "integrity": "sha512-nXnOjXaORcj6zDsvv3YzohSn46yUuBRZen5+Y7temBf/zaxkOx/3gtR2/nG/A67FhVxZIHychxQ+mwsVbAyLBA==",
"requires": {
- "async": "^3.2.3",
- "cassandra-driver": "^4.6.3",
+ "async": "^3.2.4",
+ "cassandra-driver": "^4.6.4",
"dirty": "^1.1.3",
- "elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
+ "elasticsearch8": "npm:@elastic/elasticsearch@^8.8.1",
+ "eslint-plugin-import": "^2.26.0",
"mongodb": "^3.7.3",
- "mssql": "^8.1.0",
+ "mssql": "^9.1.1",
"mysql": "2.18.1",
- "nano": "^10.0.0",
- "pg": "^8.7.3",
- "redis": "^4.1.0",
+ "nano": "^10.1.2",
+ "pg": "^8.11.1",
+ "redis": "^4.6.7",
"rethinkdb": "^2.4.2",
- "simple-git": "^3.7.1",
- "sqlite3": "^5.0.6"
+ "semver": "^7.5.3",
+ "simple-git": "^3.19.1",
+ "sqlite3": "^5.1.6"
},
"dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "optional": true
+ "array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ }
},
- "are-we-there-yet": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
- "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
- "optional": true,
+ "array.prototype.flat": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+ "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
"requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^3.6.0"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
}
},
- "axios": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
- "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
- "follow-redirects": "^1.15.0",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
+ "ms": "^2.1.1"
}
},
- "gauge": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
- "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
- "optional": true,
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
"requires": {
- "aproba": "^1.0.3 || ^2.0.0",
- "color-support": "^1.1.3",
- "console-control-strings": "^1.1.0",
- "has-unicode": "^2.0.1",
- "signal-exit": "^3.0.7",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1",
- "wide-align": "^1.1.5"
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
}
},
- "nano": {
- "version": "10.1.2",
- "resolved": "https://registry.npmjs.org/nano/-/nano-10.1.2.tgz",
- "integrity": "sha512-P3zSoD/sxAgDs/IE9eqpeAXqTdQ/gA9e9dnzaltr4A3WUo/n+eh66T873L+md5v8lXOutX/7dvcHFOO22f5hDw==",
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
"requires": {
- "axios": "^1.2.2",
- "node-abort-controller": "^3.0.1",
- "qs": "^6.11.0"
+ "esutils": "^2.0.2"
}
},
- "node-addon-api": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
- "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==",
- "optional": true
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz",
+ "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==",
+ "requires": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.11.0",
+ "resolve": "^1.22.1"
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
+ "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
+ "requires": {
+ "debug": "^3.2.7"
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.27.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
+ "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
+ "requires": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "array.prototype.flatmap": "^1.3.1",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.7.4",
+ "has": "^1.0.3",
+ "is-core-module": "^2.11.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.values": "^1.1.6",
+ "resolve": "^1.22.1",
+ "semver": "^6.3.0",
+ "tsconfig-paths": "^3.14.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ }
+ }
},
- "node-gyp": {
- "version": "8.4.1",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
- "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==",
- "optional": true,
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ=="
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
"requires": {
- "env-paths": "^2.2.0",
- "glob": "^7.1.4",
- "graceful-fs": "^4.2.6",
- "make-fetch-happen": "^9.1.0",
- "nopt": "^5.0.0",
- "npmlog": "^6.0.0",
- "rimraf": "^3.0.2",
- "semver": "^7.3.5",
- "tar": "^6.1.2",
- "which": "^2.0.2"
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
}
},
- "npmlog": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
- "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
- "optional": true,
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
"requires": {
- "are-we-there-yet": "^3.0.0",
- "console-control-strings": "^1.1.0",
- "gauge": "^4.0.3",
- "set-blocking": "^2.0.0"
+ "call-bind": "^1.0.2"
}
},
- "readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "optional": true,
+ "json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
+ "minimist": "^1.2.0"
}
},
- "redis": {
- "version": "4.6.7",
- "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz",
- "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==",
+ "minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
"requires": {
- "@redis/bloom": "1.2.0",
- "@redis/client": "1.5.8",
- "@redis/graph": "1.1.0",
- "@redis/json": "1.0.4",
- "@redis/search": "1.1.3",
- "@redis/time-series": "1.0.4"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
}
},
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "optional": true
+ "object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
},
- "sqlite3": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz",
- "integrity": "sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==",
- "optional": true,
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
"requires": {
- "@mapbox/node-pre-gyp": "^1.0.0",
- "node-addon-api": "^4.2.0",
- "node-gyp": "8.x",
- "tar": "^6.1.11"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
}
},
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "optional": true,
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
"requires": {
- "safe-buffer": "~5.2.0"
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
}
},
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "optional": true,
+ "tsconfig-paths": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
+ "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==",
"requires": {
- "ansi-regex": "^5.0.1"
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
}
}
}
@@ -9667,28 +13478,63 @@
}
},
"unbox-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
- "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
+ "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
"requires": {
- "call-bind": "^1.0.2",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.0.3",
+ "function-bind": "^1.1.1",
+ "has-bigints": "^1.0.1",
+ "has-symbols": "^1.0.2",
"which-boxed-primitive": "^1.0.2"
- },
- "dependencies": {
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
- }
}
},
+ "undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true
+ },
"underscore": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz",
"integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA=="
},
+ "undici": {
+ "version": "5.22.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz",
+ "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==",
+ "requires": {
+ "busboy": "^1.6.0"
+ }
+ },
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==",
+ "dev": true
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "dev": true,
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+ "dev": true
+ },
"unified": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.1.tgz",
@@ -9735,9 +13581,9 @@
}
},
"universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
},
"unorm": {
"version": "1.6.0",
@@ -9749,6 +13595,22 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
+ "untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true
+ },
+ "update-browserslist-db": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
+ "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
+ "dev": true,
+ "requires": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ }
+ },
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -9757,10 +13619,19 @@
"punycode": "^2.1.0"
}
},
+ "url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"utils-merge": {
"version": "1.0.1",
@@ -9772,12 +13643,6 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
- "v8-compile-cache": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
- "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
- "dev": true
- },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -9786,7 +13651,7 @@
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
@@ -9796,7 +13661,7 @@
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
}
}
},
@@ -9907,6 +13772,19 @@
"is-symbol": "^1.0.3"
}
},
+ "which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ }
+ },
"wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -9916,11 +13794,6 @@
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
- "word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
- },
"workerpool": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz",
@@ -9936,75 +13809,17 @@
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
- "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg=="
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA=="
},
"wtfnode": {
"version": "0.9.1",
@@ -10016,11 +13831,6 @@
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="
},
- "xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
- },
"xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
@@ -10048,58 +13858,24 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"requires": {
- "cliui": "^7.0.2",
+ "cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "string-width": "^4.2.0",
+ "string-width": "^4.2.3",
"y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
+ "yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
- "version": "20.2.4",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
- "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
"yargs-unparser": {
@@ -10125,7 +13901,7 @@
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
- "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+ "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
},
"yocto-queue": {
"version": "0.1.0",
diff --git a/src/package.json b/src/package.json
index ca9ef5a8a4e..03fe6f5c02f 100644
--- a/src/package.json
+++ b/src/package.json
@@ -30,7 +30,9 @@
}
],
"dependencies": {
+ "@rollup/plugin-json": "^6.0.0",
"async": "^3.2.2",
+ "cjstoesm": "^2.1.2",
"clean-css": "^5.3.2",
"cookie-parser": "^1.4.6",
"cross-spawn": "^7.0.3",
@@ -45,14 +47,14 @@
"formidable": "^2.1.2",
"http-errors": "^2.0.0",
"js-cookie": "^3.0.5",
+ "npm": "^6.14.18",
"jsdom": "^19.0.0",
"jsonminify": "0.4.2",
"languages4translatewiki": "0.1.3",
"lodash.clonedeep": "4.5.0",
- "log4js": "0.6.38",
+ "log4js": "^6.9.1",
"measured-core": "^2.0.0",
"mime-types": "^2.1.35",
- "npm": "^6.14.15",
"openapi-backend": "^5.9.2",
"proxy-addr": "^2.0.7",
"rate-limiter-flexible": "^2.4.1",
@@ -60,7 +62,7 @@
"rehype-minify-whitespace": "^5.0.1",
"request": "2.88.2",
"resolve": "1.22.2",
- "security": "1.0.0",
+ "security": "^1.0.0",
"semver": "^7.5.2",
"socket.io": "^2.4.1",
"superagent": "^8.0.9",
@@ -78,19 +80,36 @@
"etherpad-lite": "node/server.js"
},
"devDependencies": {
+ "@babel/cli": "^7.22.5",
+ "@babel/core": "^7.22.5",
+ "@babel/preset-env": "^7.22.5",
+ "@babel/register": "^7.22.5",
+ "@types/cross-spawn": "^6.0.2",
+ "@types/express": "4.17.17",
+ "@types/jquery": "^3.5.16",
+ "@types/js-cookie": "^3.0.3",
+ "@types/node": "^20.3.1",
+ "@types/underscore": "^1.11.5",
+ "concurrently": "^8.2.0",
"eslint": "^8.14.0",
"eslint-config-etherpad": "^3.0.13",
"etherpad-cli-client": "^2.0.1",
+ "i18next": "^23.2.3",
+ "i18next-fs-backend": "^2.1.5",
"mocha": "^9.2.2",
"mocha-froth": "^0.2.10",
"nodeify": "^1.0.1",
+ "nodemon": "^2.0.22",
"openapi-schema-validation": "^0.4.2",
"selenium-webdriver": "^4.10.0",
"set-cookie-parser": "^2.4.8",
"sinon": "^13.0.2",
"split-grid": "^1.0.11",
"supertest": "^6.3.3",
- "typescript": "^4.9.5"
+ "typescript": "^4.9.5",
+ "rollup-plugin-typescript2": "^0.35.0",
+ "rollup-plugin-copy": "^3.4.0",
+ "@rollup/plugin-commonjs": "^25.0.2"
},
"engines": {
"node": ">=14.15.0",
@@ -102,8 +121,10 @@
},
"scripts": {
"lint": "eslint .",
- "test": "mocha --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
- "test-container": "mocha --timeout 5000 tests/container/specs/api"
+ "test": "mocha --require @babel/register --timeout 120000 --recursive tests/backend/specs ../node_modules/ep_*/static/tests/backend/specs",
+ "test-container": "mocha --timeout 5000 tests/container/specs/api",
+ "dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/server.js\"",
+ "build": "npx rollup -c rollup.config.js"
},
"version": "1.9.0",
"license": "Apache-2.0"
diff --git a/src/rollup.config.js b/src/rollup.config.js
new file mode 100644
index 00000000000..aeef504a3c5
--- /dev/null
+++ b/src/rollup.config.js
@@ -0,0 +1,32 @@
+const typescript = require('rollup-plugin-typescript2');
+const copy = require('rollup-plugin-copy');
+const glob = require('glob');
+const json = require('@rollup/plugin-json')
+const commonJS = require('@rollup/plugin-commonjs')
+
+
+module.exports = {
+ input: './node/server.ts', // Matches all TypeScript files in the 'src' directory and its subdirectories
+ output: {
+ preserveModules: true,
+ dir: './dist',
+ format: 'cjs',
+ },
+ plugins: [
+ json(),
+ typescript({
+ tsconfig: 'tsconfig.json',
+ }),
+ commonJS(),
+ copy({
+ targets: [
+ {src:'./package.json', dest:'./dist'},
+ { src: './LICENSE', dest: './dist' },
+ { src: './src/locales/*', dest: './dist/locales' },
+ { src: './src/static/css/*', dest: './dist/static/css' },
+ { src: './src/templates', dest: './dist/templates' },
+ {src:'./ep.json', dest:'./dist'},
+ ]
+ })
+ ],
+};
\ No newline at end of file
diff --git a/src/static/favicon.ico b/src/static/favicon.ico
index c33f2e614c6..e69de29bb2d 100644
Binary files a/src/static/favicon.ico and b/src/static/favicon.ico differ
diff --git a/src/static/font/Aleygreya-ExtraBold.woff b/src/static/font/Aleygreya-ExtraBold.woff
index b1e0423bbb9..e69de29bb2d 100644
Binary files a/src/static/font/Aleygreya-ExtraBold.woff and b/src/static/font/Aleygreya-ExtraBold.woff differ
diff --git a/src/static/font/Aleygreya-ExtraBold.woff2 b/src/static/font/Aleygreya-ExtraBold.woff2
index f66acbaf8be..e69de29bb2d 100644
Binary files a/src/static/font/Aleygreya-ExtraBold.woff2 and b/src/static/font/Aleygreya-ExtraBold.woff2 differ
diff --git a/src/static/font/Aleygreya-Medium.woff b/src/static/font/Aleygreya-Medium.woff
index 0403d5714c2..e69de29bb2d 100644
Binary files a/src/static/font/Aleygreya-Medium.woff and b/src/static/font/Aleygreya-Medium.woff differ
diff --git a/src/static/font/Aleygreya-Medium.woff2 b/src/static/font/Aleygreya-Medium.woff2
index 5056666ba77..e69de29bb2d 100644
Binary files a/src/static/font/Aleygreya-Medium.woff2 and b/src/static/font/Aleygreya-Medium.woff2 differ
diff --git a/src/static/font/Montserrat-Light.otf b/src/static/font/Montserrat-Light.otf
index f2f0e2df089..e69de29bb2d 100644
Binary files a/src/static/font/Montserrat-Light.otf and b/src/static/font/Montserrat-Light.otf differ
diff --git a/src/static/font/Montserrat-Regular.otf b/src/static/font/Montserrat-Regular.otf
index f61d57ec133..e69de29bb2d 100644
Binary files a/src/static/font/Montserrat-Regular.otf and b/src/static/font/Montserrat-Regular.otf differ
diff --git a/src/static/font/Quicksand-Bold.ttf b/src/static/font/Quicksand-Bold.ttf
index 49326cda826..e69de29bb2d 100644
Binary files a/src/static/font/Quicksand-Bold.ttf and b/src/static/font/Quicksand-Bold.ttf differ
diff --git a/src/static/font/Quicksand-Medium.ttf b/src/static/font/Quicksand-Medium.ttf
index 7dc8c2745dd..e69de29bb2d 100644
Binary files a/src/static/font/Quicksand-Medium.ttf and b/src/static/font/Quicksand-Medium.ttf differ
diff --git a/src/static/font/Quicksand-Regular.ttf b/src/static/font/Quicksand-Regular.ttf
index 9fdce17db50..e69de29bb2d 100644
Binary files a/src/static/font/Quicksand-Regular.ttf and b/src/static/font/Quicksand-Regular.ttf differ
diff --git a/src/static/font/Roboto-Bold.ttf b/src/static/font/Roboto-Bold.ttf
index d998cf5b468..e69de29bb2d 100644
Binary files a/src/static/font/Roboto-Bold.ttf and b/src/static/font/Roboto-Bold.ttf differ
diff --git a/src/static/font/Roboto-Regular.ttf b/src/static/font/Roboto-Regular.ttf
index 2b6392ffe87..e69de29bb2d 100644
Binary files a/src/static/font/Roboto-Regular.ttf and b/src/static/font/Roboto-Regular.ttf differ
diff --git a/src/static/font/RobotoMono-Bold.ttf b/src/static/font/RobotoMono-Bold.ttf
index 07ef607d50c..e69de29bb2d 100644
Binary files a/src/static/font/RobotoMono-Bold.ttf and b/src/static/font/RobotoMono-Bold.ttf differ
diff --git a/src/static/font/RobotoMono-Regular.ttf b/src/static/font/RobotoMono-Regular.ttf
index b158a334eb3..e69de29bb2d 100644
Binary files a/src/static/font/RobotoMono-Regular.ttf and b/src/static/font/RobotoMono-Regular.ttf differ
diff --git a/src/static/font/fontawesome-etherpad.eot b/src/static/font/fontawesome-etherpad.eot
index 2f2b4e8be37..e69de29bb2d 100644
Binary files a/src/static/font/fontawesome-etherpad.eot and b/src/static/font/fontawesome-etherpad.eot differ
diff --git a/src/static/font/fontawesome-etherpad.ttf b/src/static/font/fontawesome-etherpad.ttf
index 120d72500e6..e69de29bb2d 100644
Binary files a/src/static/font/fontawesome-etherpad.ttf and b/src/static/font/fontawesome-etherpad.ttf differ
diff --git a/src/static/font/fontawesome-etherpad.woff b/src/static/font/fontawesome-etherpad.woff
index 1d21170939a..e69de29bb2d 100644
Binary files a/src/static/font/fontawesome-etherpad.woff and b/src/static/font/fontawesome-etherpad.woff differ
diff --git a/src/static/font/fontawesome-etherpad.woff2 b/src/static/font/fontawesome-etherpad.woff2
index 72eb1da718d..e69de29bb2d 100644
Binary files a/src/static/font/fontawesome-etherpad.woff2 and b/src/static/font/fontawesome-etherpad.woff2 differ
diff --git a/src/static/font/opendyslexic.otf b/src/static/font/opendyslexic.otf
index 1a7c9d411b2..e69de29bb2d 100644
Binary files a/src/static/font/opendyslexic.otf and b/src/static/font/opendyslexic.otf differ
diff --git a/src/static/img/brand.svg b/src/static/img/brand.svg
old mode 100755
new mode 100644
diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js
deleted file mode 100644
index f508af64111..00000000000
--- a/src/static/js/AttributeManager.js
+++ /dev/null
@@ -1,382 +0,0 @@
-'use strict';
-
-const AttributeMap = require('./AttributeMap');
-const Changeset = require('./Changeset');
-const ChangesetUtils = require('./ChangesetUtils');
-const attributes = require('./attributes');
-const _ = require('./underscore');
-
-const lineMarkerAttribute = 'lmkr';
-
-// Some of these attributes are kept for compatibility purposes.
-// Not sure if we need all of them
-const DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
-
-// If one of these attributes are set to the first character of a
-// line it is considered as a line attribute marker i.e. attributes
-// set on this marker are applied to the whole line.
-// The list attribute is only maintained for compatibility reasons
-const lineAttributes = [lineMarkerAttribute, 'list'];
-
-/*
- The Attribute manager builds changesets based on a document
- representation for setting and removing range or line-based attributes.
-
- @param rep the document representation to be used
- @param applyChangesetCallback this callback will be called
- once a changeset has been built.
-
-
- A document representation contains
- - an array `alines` containing 1 attributes string for each line
- - an Attribute pool `apool`
- - a SkipList `lines` containing the text lines of the document.
-*/
-
-const AttributeManager = function (rep, applyChangesetCallback) {
- this.rep = rep;
- this.applyChangesetCallback = applyChangesetCallback;
- this.author = '';
-
- // If the first char in a line has one of the following attributes
- // it will be considered as a line marker
-};
-
-AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES;
-AttributeManager.lineAttributes = lineAttributes;
-
-AttributeManager.prototype = _(AttributeManager.prototype).extend({
-
- applyChangeset(changeset) {
- if (!this.applyChangesetCallback) return changeset;
-
- const cs = changeset.toString();
- if (!Changeset.isIdentity(cs)) {
- this.applyChangesetCallback(cs);
- }
-
- return changeset;
- },
-
- /*
- Sets attributes on a range
- @param start [row, col] tuple pointing to the start of the range
- @param end [row, col] tuple pointing to the end of the range
- @param attribs: an array of attributes
- */
- setAttributesOnRange(start, end, attribs) {
- if (start[0] < 0) throw new RangeError('selection start line number is negative');
- if (start[1] < 0) throw new RangeError('selection start column number is negative');
- if (end[0] < 0) throw new RangeError('selection end line number is negative');
- if (end[1] < 0) throw new RangeError('selection end column number is negative');
- if (start[0] > end[0] || (start[0] === end[0] && start[1] > end[1])) {
- throw new RangeError('selection ends before it starts');
- }
-
- // instead of applying the attributes to the whole range at once, we need to apply them
- // line by line, to be able to disregard the "*" used as line marker. For more details,
- // see https://github.com/ether/etherpad-lite/issues/2772
- let allChangesets;
- for (let row = start[0]; row <= end[0]; row++) {
- const [startCol, endCol] = this._findRowRange(row, start, end);
- const rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
-
- // compose changesets of all rows into a single changeset
- // as the range might not be continuous
- // due to the presence of line markers on the rows
- if (allChangesets) {
- allChangesets = Changeset.compose(
- allChangesets.toString(), rowChangeset.toString(), this.rep.apool);
- } else {
- allChangesets = rowChangeset;
- }
- }
-
- return this.applyChangeset(allChangesets);
- },
-
- _findRowRange(row, start, end) {
- if (row < start[0] || row > end[0]) throw new RangeError(`line ${row} not in selection`);
- if (row >= this.rep.lines.length()) throw new RangeError(`selected line ${row} does not exist`);
-
- // Subtract 1 for the end-of-line '\n' (it is never selected).
- const lineLength =
- this.rep.lines.offsetOfIndex(row + 1) - this.rep.lines.offsetOfIndex(row) - 1;
- const markerWidth = this.lineHasMarker(row) ? 1 : 0;
- if (lineLength - markerWidth < 0) throw new Error(`line ${row} has negative length`);
-
- if (start[1] < 0) throw new RangeError('selection starts at negative column');
- const startCol = Math.max(markerWidth, row === start[0] ? start[1] : 0);
- if (startCol > lineLength) throw new RangeError('selection starts after line end');
-
- if (end[1] < 0) throw new RangeError('selection ends at negative column');
- const endCol = Math.max(markerWidth, row === end[0] ? end[1] : lineLength);
- if (endCol > lineLength) throw new RangeError('selection ends after line end');
- if (startCol > endCol) throw new RangeError('selection ends before it starts');
-
- return [startCol, endCol];
- },
-
- /**
- * Sets attributes on a range, by line
- * @param row the row where range is
- * @param startCol column where range starts
- * @param endCol column where range ends (one past the last selected column)
- * @param attribs an array of attributes
- */
- _setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
- const builder = Changeset.builder(this.rep.lines.totalWidth());
- ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
- ChangesetUtils.buildKeepRange(
- this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
- return builder;
- },
-
- /*
- Returns if the line already has a line marker
- @param lineNum: the number of the line
- */
- lineHasMarker(lineNum) {
- return lineAttributes.find(
- (attribute) => this.getAttributeOnLine(lineNum, attribute) !== '') !== undefined;
- },
-
- /*
- Gets a specified attribute on a line
- @param lineNum: the number of the line to set the attribute for
- @param attributeKey: the name of the attribute to get, e.g. list
- */
- getAttributeOnLine(lineNum, attributeName) {
- // get `attributeName` attribute of first char of line
- const aline = this.rep.alines[lineNum];
- if (!aline) return '';
- const [op] = Changeset.deserializeOps(aline);
- if (op == null) return '';
- return AttributeMap.fromString(op.attribs, this.rep.apool).get(attributeName) || '';
- },
-
- /*
- Gets all attributes on a line
- @param lineNum: the number of the line to get the attribute for
- */
- getAttributesOnLine(lineNum) {
- // get attributes of first char of line
- const aline = this.rep.alines[lineNum];
- if (!aline) return [];
- const [op] = Changeset.deserializeOps(aline);
- if (op == null) return [];
- return [...attributes.attribsFromString(op.attribs, this.rep.apool)];
- },
-
- /*
- Gets a given attribute on a selection
- @param attributeName
- @param prevChar
- returns true or false if an attribute is visible in range
- */
- getAttributeOnSelection(attributeName, prevChar) {
- const rep = this.rep;
- if (!(rep.selStart && rep.selEnd)) return;
- // If we're looking for the caret attribute not the selection
- // has the user already got a selection or is this purely a caret location?
- const isNotSelection = (rep.selStart[0] === rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
- if (isNotSelection) {
- if (prevChar) {
- // If it's not the start of the line
- if (rep.selStart[1] !== 0) {
- rep.selStart[1]--;
- }
- }
- }
-
- const withIt = new AttributeMap(rep.apool).set(attributeName, 'true').toString();
- const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
- const hasIt = (attribs) => withItRegex.test(attribs);
-
- const rangeHasAttrib = (selStart, selEnd) => {
- // if range is collapsed -> no attribs in range
- if (selStart[1] === selEnd[1] && selStart[0] === selEnd[0]) return false;
-
- if (selStart[0] !== selEnd[0]) { // -> More than one line selected
- // from selStart to the end of the first line
- let hasAttrib = rangeHasAttrib(
- selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
-
- // for all lines in between
- for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
- hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
- }
-
- // for the last, potentially partial, line
- hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
-
- return hasAttrib;
- }
-
- // Logic tells us we now have a range on a single line
-
- const lineNum = selStart[0];
- const start = selStart[1];
- const end = selEnd[1];
- let hasAttrib = true;
-
- let indexIntoLine = 0;
- for (const op of Changeset.deserializeOps(rep.alines[lineNum])) {
- const opStartInLine = indexIntoLine;
- const opEndInLine = opStartInLine + op.chars;
- if (!hasIt(op.attribs)) {
- // does op overlap selection?
- if (!(opEndInLine <= start || opStartInLine >= end)) {
- // since it's overlapping but hasn't got the attrib -> range hasn't got it
- hasAttrib = false;
- break;
- }
- }
- indexIntoLine = opEndInLine;
- }
-
- return hasAttrib;
- };
- return rangeHasAttrib(rep.selStart, rep.selEnd);
- },
-
- /*
- Gets all attributes at a position containing line number and column
- @param lineNumber starting with zero
- @param column starting with zero
- returns a list of attributes in the format
- [ ["key","value"], ["key","value"], ... ]
- */
- getAttributesOnPosition(lineNumber, column) {
- // get all attributes of the line
- const aline = this.rep.alines[lineNumber];
-
- if (!aline) {
- return [];
- }
-
- // we need to sum up how much characters each operations take until the wanted position
- let currentPointer = 0;
-
- for (const currentOperation of Changeset.deserializeOps(aline)) {
- currentPointer += currentOperation.chars;
- if (currentPointer <= column) continue;
- return [...attributes.attribsFromString(currentOperation.attribs, this.rep.apool)];
- }
- return [];
- },
-
- /*
- Gets all attributes at caret position
- if the user selected a range, the start of the selection is taken
- returns a list of attributes in the format
- [ ["key","value"], ["key","value"], ... ]
- */
- getAttributesOnCaret() {
- return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
- },
-
- /*
- Sets a specified attribute on a line
- @param lineNum: the number of the line to set the attribute for
- @param attributeKey: the name of the attribute to set, e.g. list
- @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
-
- */
- setAttributeOnLine(lineNum, attributeName, attributeValue) {
- let loc = [0, 0];
- const builder = Changeset.builder(this.rep.lines.totalWidth());
- const hasMarker = this.lineHasMarker(lineNum);
-
- ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
-
- if (hasMarker) {
- ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
- [attributeName, attributeValue],
- ], this.rep.apool);
- } else {
- // add a line marker
- builder.insert('*', [
- ['author', this.author],
- ['insertorder', 'first'],
- [lineMarkerAttribute, '1'],
- [attributeName, attributeValue],
- ], this.rep.apool);
- }
-
- return this.applyChangeset(builder);
- },
-
- /**
- * Removes a specified attribute on a line
- * @param lineNum the number of the affected line
- * @param attributeName the name of the attribute to remove, e.g. list
- * @param attributeValue if given only attributes with equal value will be removed
- */
- removeAttributeOnLine(lineNum, attributeName, attributeValue) {
- const builder = Changeset.builder(this.rep.lines.totalWidth());
- const hasMarker = this.lineHasMarker(lineNum);
- let found = false;
-
- const attribs = this.getAttributesOnLine(lineNum).map((attrib) => {
- if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) {
- found = true;
- return [attrib[0], ''];
- } else if (attrib[0] === 'author') {
- // update last author to make changes to line attributes on this line
- return [attrib[0], this.author];
- }
- return attrib;
- });
-
- if (!found) {
- return;
- }
-
- ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
-
- const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1])
- .map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
-
- // if we have marker and any of attributes don't need to have marker. we need delete it
- if (hasMarker && !countAttribsWithMarker) {
- ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
- } else {
- ChangesetUtils.buildKeepRange(
- this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
- }
-
- return this.applyChangeset(builder);
- },
-
- /*
- Toggles a line attribute for the specified line number
- If a line attribute with the specified name exists with any value it will be removed
- Otherwise it will be set to the given value
- @param lineNum: the number of the line to toggle the attribute for
- @param attributeKey: the name of the attribute to toggle, e.g. list
- @param attributeValue: the value to pass to the attribute (e.g. indention level)
- */
- toggleAttributeOnLine(lineNum, attributeName, attributeValue) {
- return this.getAttributeOnLine(lineNum, attributeName)
- ? this.removeAttributeOnLine(lineNum, attributeName)
- : this.setAttributeOnLine(lineNum, attributeName, attributeValue);
- },
-
- hasAttributeOnSelectionOrCaretPosition(attributeName) {
- const hasSelection = (
- (this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1])
- );
- let hasAttrib;
- if (hasSelection) {
- hasAttrib = this.getAttributeOnSelection(attributeName);
- } else {
- const attributesOnCaretPosition = this.getAttributesOnCaret();
- const allAttribs = [].concat(...attributesOnCaretPosition); // flatten
- hasAttrib = allAttribs.includes(attributeName);
- }
- return hasAttrib;
- },
-});
-
-module.exports = AttributeManager;
diff --git a/src/static/js/AttributeManager.ts b/src/static/js/AttributeManager.ts
new file mode 100644
index 00000000000..67a71c0ee6a
--- /dev/null
+++ b/src/static/js/AttributeManager.ts
@@ -0,0 +1,346 @@
+import AttributeMap from "./AttributeMap.js";
+import * as Changeset from "./Changeset.js";
+import * as ChangesetUtils from "./ChangesetUtils.js";
+import * as attributes from "./attributes.js";
+import _ from "underscore";
+export { map, each, identity} from "underscore";
+
+'use strict';
+const lineMarkerAttribute = 'lmkr';
+// Some of these attributes are kept for compatibility purposes.
+// Not sure if we need all of them
+const DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
+// If one of these attributes are set to the first character of a
+// line it is considered as a line attribute marker i.e. attributes
+// set on this marker are applied to the whole line.
+// The list attribute is only maintained for compatibility reasons
+const lineAttributes = [lineMarkerAttribute, 'list'];
+/*
+ The Attribute manager builds changesets based on a document
+ representation for setting and removing range or line-based attributes.
+
+ @param rep the document representation to be used
+ @param applyChangesetCallback this callback will be called
+ once a changeset has been built.
+
+
+ A document representation contains
+ - an array `alines` containing 1 attributes string for each line
+ - an Attribute pool `apool`
+ - a SkipList `lines` containing the text lines of the document.
+*/
+const AttributeManager = function (rep, applyChangesetCallback) {
+ this.rep = rep;
+ this.applyChangesetCallback = applyChangesetCallback;
+ this.author = '';
+ // If the first char in a line has one of the following attributes
+ // it will be considered as a line marker
+};
+AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES;
+AttributeManager.lineAttributes = lineAttributes;
+AttributeManager.prototype = _(AttributeManager.prototype).extend({
+ applyChangeset(changeset) {
+ if (!this.applyChangesetCallback)
+ return changeset;
+ const cs = changeset.toString();
+ if (!Changeset.isIdentity(cs)) {
+ this.applyChangesetCallback(cs);
+ }
+ return changeset;
+ },
+ /*
+ Sets attributes on a range
+ @param start [row, col] tuple pointing to the start of the range
+ @param end [row, col] tuple pointing to the end of the range
+ @param attribs: an array of attributes
+ */
+ setAttributesOnRange(start, end, attribs) {
+ if (start[0] < 0)
+ throw new RangeError('selection start line number is negative');
+ if (start[1] < 0)
+ throw new RangeError('selection start column number is negative');
+ if (end[0] < 0)
+ throw new RangeError('selection end line number is negative');
+ if (end[1] < 0)
+ throw new RangeError('selection end column number is negative');
+ if (start[0] > end[0] || (start[0] === end[0] && start[1] > end[1])) {
+ throw new RangeError('selection ends before it starts');
+ }
+ // instead of applying the attributes to the whole range at once, we need to apply them
+ // line by line, to be able to disregard the "*" used as line marker. For more details,
+ // see https://github.com/ether/etherpad-lite/issues/2772
+ let allChangesets;
+ for (let row = start[0]; row <= end[0]; row++) {
+ const [startCol, endCol] = this._findRowRange(row, start, end);
+ const rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
+ // compose changesets of all rows into a single changeset
+ // as the range might not be continuous
+ // due to the presence of line markers on the rows
+ if (allChangesets) {
+ allChangesets = Changeset.compose(allChangesets.toString(), rowChangeset.toString(), this.rep.apool);
+ }
+ else {
+ allChangesets = rowChangeset;
+ }
+ }
+ return this.applyChangeset(allChangesets);
+ },
+ _findRowRange(row, start, end) {
+ if (row < start[0] || row > end[0])
+ throw new RangeError(`line ${row} not in selection`);
+ if (row >= this.rep.lines.length())
+ throw new RangeError(`selected line ${row} does not exist`);
+ // Subtract 1 for the end-of-line '\n' (it is never selected).
+ const lineLength = this.rep.lines.offsetOfIndex(row + 1) - this.rep.lines.offsetOfIndex(row) - 1;
+ const markerWidth = this.lineHasMarker(row) ? 1 : 0;
+ if (lineLength - markerWidth < 0)
+ throw new Error(`line ${row} has negative length`);
+ if (start[1] < 0)
+ throw new RangeError('selection starts at negative column');
+ const startCol = Math.max(markerWidth, row === start[0] ? start[1] : 0);
+ if (startCol > lineLength)
+ throw new RangeError('selection starts after line end');
+ if (end[1] < 0)
+ throw new RangeError('selection ends at negative column');
+ const endCol = Math.max(markerWidth, row === end[0] ? end[1] : lineLength);
+ if (endCol > lineLength)
+ throw new RangeError('selection ends after line end');
+ if (startCol > endCol)
+ throw new RangeError('selection ends before it starts');
+ return [startCol, endCol];
+ },
+ /**
+ * Sets attributes on a range, by line
+ * @param row the row where range is
+ * @param startCol column where range starts
+ * @param endCol column where range ends (one past the last selected column)
+ * @param attribs an array of attributes
+ */
+ _setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
+ const builder = Changeset.builder(this.rep.lines.totalWidth());
+ ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
+ ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
+ return builder;
+ },
+ /*
+ Returns if the line already has a line marker
+ @param lineNum: the number of the line
+ */
+ lineHasMarker(lineNum) {
+ return lineAttributes.find((attribute) => this.getAttributeOnLine(lineNum, attribute) !== '') !== undefined;
+ },
+ /*
+ Gets a specified attribute on a line
+ @param lineNum: the number of the line to set the attribute for
+ @param attributeKey: the name of the attribute to get, e.g. list
+ */
+ getAttributeOnLine(lineNum, attributeName) {
+ // get `attributeName` attribute of first char of line
+ const aline = this.rep.alines[lineNum];
+ if (!aline)
+ return '';
+ const [op] = Changeset.deserializeOps(aline);
+ if (op == null)
+ return '';
+ return AttributeMap.fromString(op.attribs, this.rep.apool).get(attributeName) || '';
+ },
+ /*
+ Gets all attributes on a line
+ @param lineNum: the number of the line to get the attribute for
+ */
+ getAttributesOnLine(lineNum) {
+ // get attributes of first char of line
+ const aline = this.rep.alines[lineNum];
+ if (!aline)
+ return [];
+ const [op] = Changeset.deserializeOps(aline);
+ if (op == null)
+ return [];
+ return [...attributes.attribsFromString(op.attribs, this.rep.apool)];
+ },
+ /*
+ Gets a given attribute on a selection
+ @param attributeName
+ @param prevChar
+ returns true or false if an attribute is visible in range
+ */
+ getAttributeOnSelection(attributeName, prevChar) {
+ const rep = this.rep;
+ if (!(rep.selStart && rep.selEnd))
+ return;
+ // If we're looking for the caret attribute not the selection
+ // has the user already got a selection or is this purely a caret location?
+ const isNotSelection = (rep.selStart[0] === rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
+ if (isNotSelection) {
+ if (prevChar) {
+ // If it's not the start of the line
+ if (rep.selStart[1] !== 0) {
+ rep.selStart[1]--;
+ }
+ }
+ }
+ const withIt = new AttributeMap(rep.apool).set(attributeName, 'true').toString();
+ const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
+ const hasIt = (attribs) => withItRegex.test(attribs);
+ const rangeHasAttrib = (selStart, selEnd) => {
+ // if range is collapsed -> no attribs in range
+ if (selStart[1] === selEnd[1] && selStart[0] === selEnd[0])
+ return false;
+ if (selStart[0] !== selEnd[0]) { // -> More than one line selected
+ // from selStart to the end of the first line
+ let hasAttrib = rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
+ // for all lines in between
+ for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
+ hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
+ }
+ // for the last, potentially partial, line
+ hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
+ return hasAttrib;
+ }
+ // Logic tells us we now have a range on a single line
+ const lineNum = selStart[0];
+ const start = selStart[1];
+ const end = selEnd[1];
+ let hasAttrib = true;
+ let indexIntoLine = 0;
+ for (const op of Changeset.deserializeOps(rep.alines[lineNum])) {
+ const opStartInLine = indexIntoLine;
+ const opEndInLine = opStartInLine + op.chars;
+ if (!hasIt(op.attribs)) {
+ // does op overlap selection?
+ if (!(opEndInLine <= start || opStartInLine >= end)) {
+ // since it's overlapping but hasn't got the attrib -> range hasn't got it
+ hasAttrib = false;
+ break;
+ }
+ }
+ indexIntoLine = opEndInLine;
+ }
+ return hasAttrib;
+ };
+ return rangeHasAttrib(rep.selStart, rep.selEnd);
+ },
+ /*
+ Gets all attributes at a position containing line number and column
+ @param lineNumber starting with zero
+ @param column starting with zero
+ returns a list of attributes in the format
+ [ ["key","value"], ["key","value"], ... ]
+ */
+ getAttributesOnPosition(lineNumber, column) {
+ // get all attributes of the line
+ const aline = this.rep.alines[lineNumber];
+ if (!aline) {
+ return [];
+ }
+ // we need to sum up how much characters each operations take until the wanted position
+ let currentPointer = 0;
+ for (const currentOperation of Changeset.deserializeOps(aline)) {
+ currentPointer += currentOperation.chars;
+ if (currentPointer <= column)
+ continue;
+ return [...attributes.attribsFromString(currentOperation.attribs, this.rep.apool)];
+ }
+ return [];
+ },
+ /*
+ Gets all attributes at caret position
+ if the user selected a range, the start of the selection is taken
+ returns a list of attributes in the format
+ [ ["key","value"], ["key","value"], ... ]
+ */
+ getAttributesOnCaret() {
+ return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
+ },
+ /*
+ Sets a specified attribute on a line
+ @param lineNum: the number of the line to set the attribute for
+ @param attributeKey: the name of the attribute to set, e.g. list
+ @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
+
+ */
+ setAttributeOnLine(lineNum, attributeName, attributeValue) {
+ let loc = [0, 0];
+ const builder = Changeset.builder(this.rep.lines.totalWidth());
+ const hasMarker = this.lineHasMarker(lineNum);
+ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
+ if (hasMarker) {
+ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
+ [attributeName, attributeValue],
+ ], this.rep.apool);
+ }
+ else {
+ // add a line marker
+ builder.insert('*', [
+ ['author', this.author],
+ ['insertorder', 'first'],
+ [lineMarkerAttribute, '1'],
+ [attributeName, attributeValue],
+ ], this.rep.apool);
+ }
+ return this.applyChangeset(builder);
+ },
+ /**
+ * Removes a specified attribute on a line
+ * @param lineNum the number of the affected line
+ * @param attributeName the name of the attribute to remove, e.g. list
+ * @param attributeValue if given only attributes with equal value will be removed
+ */
+ removeAttributeOnLine(lineNum, attributeName, attributeValue) {
+ const builder = Changeset.builder(this.rep.lines.totalWidth());
+ const hasMarker = this.lineHasMarker(lineNum);
+ let found = false;
+ const attribs = this.getAttributesOnLine(lineNum).map((attrib) => {
+ if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) {
+ found = true;
+ return [attrib[0], ''];
+ }
+ else if (attrib[0] === 'author') {
+ // update last author to make changes to line attributes on this line
+ return [attrib[0], this.author];
+ }
+ return attrib;
+ });
+ if (!found) {
+ return;
+ }
+ ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
+ const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1])
+ .map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
+ // if we have marker and any of attributes don't need to have marker. we need delete it
+ if (hasMarker && !countAttribsWithMarker) {
+ ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
+ }
+ else {
+ ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
+ }
+ return this.applyChangeset(builder);
+ },
+ /*
+ Toggles a line attribute for the specified line number
+ If a line attribute with the specified name exists with any value it will be removed
+ Otherwise it will be set to the given value
+ @param lineNum: the number of the line to toggle the attribute for
+ @param attributeKey: the name of the attribute to toggle, e.g. list
+ @param attributeValue: the value to pass to the attribute (e.g. indention level)
+ */
+ toggleAttributeOnLine(lineNum, attributeName, attributeValue) {
+ return this.getAttributeOnLine(lineNum, attributeName)
+ ? this.removeAttributeOnLine(lineNum, attributeName)
+ : this.setAttributeOnLine(lineNum, attributeName, attributeValue);
+ },
+ hasAttributeOnSelectionOrCaretPosition(attributeName) {
+ const hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
+ let hasAttrib;
+ if (hasSelection) {
+ hasAttrib = this.getAttributeOnSelection(attributeName);
+ }
+ else {
+ const attributesOnCaretPosition = this.getAttributesOnCaret();
+ const allAttribs = [].concat(...attributesOnCaretPosition); // flatten
+ hasAttrib = allAttribs.includes(attributeName);
+ }
+ return hasAttrib;
+ },
+});
+export default AttributeManager;
diff --git a/src/static/js/AttributeMap.js b/src/static/js/AttributeMap.js
deleted file mode 100644
index 55640eb8bc6..00000000000
--- a/src/static/js/AttributeMap.js
+++ /dev/null
@@ -1,91 +0,0 @@
-'use strict';
-
-const attributes = require('./attributes');
-
-/**
- * A `[key, value]` pair of strings describing a text attribute.
- *
- * @typedef {[string, string]} Attribute
- */
-
-/**
- * A concatenated sequence of zero or more attribute identifiers, each one represented by an
- * asterisk followed by a base-36 encoded attribute number.
- *
- * Examples: '', '*0', '*3*j*z*1q'
- *
- * @typedef {string} AttributeString
- */
-
-/**
- * Convenience class to convert an Op's attribute string to/from a Map of key, value pairs.
- */
-class AttributeMap extends Map {
- /**
- * Converts an attribute string into an AttributeMap.
- *
- * @param {AttributeString} str - The attribute string to convert into an AttributeMap.
- * @param {AttributePool} pool - Attribute pool.
- * @returns {AttributeMap}
- */
- static fromString(str, pool) {
- return new AttributeMap(pool).updateFromString(str);
- }
-
- /**
- * @param {AttributePool} pool - Attribute pool.
- */
- constructor(pool) {
- super();
- /** @public */
- this.pool = pool;
- }
-
- /**
- * @param {string} k - Attribute name.
- * @param {string} v - Attribute value.
- * @returns {AttributeMap} `this` (for chaining).
- */
- set(k, v) {
- k = k == null ? '' : String(k);
- v = v == null ? '' : String(v);
- this.pool.putAttrib([k, v]);
- return super.set(k, v);
- }
-
- toString() {
- return attributes.attribsToString(attributes.sort([...this]), this.pool);
- }
-
- /**
- * @param {Iterable} entries - [key, value] pairs to insert into this map.
- * @param {boolean} [emptyValueIsDelete] - If true and an entry's value is the empty string, the
- * key is removed from this map (if present).
- * @returns {AttributeMap} `this` (for chaining).
- */
- update(entries, emptyValueIsDelete = false) {
- for (let [k, v] of entries) {
- k = k == null ? '' : String(k);
- v = v == null ? '' : String(v);
- if (!v && emptyValueIsDelete) {
- this.delete(k);
- } else {
- this.set(k, v);
- }
- }
- return this;
- }
-
- /**
- * @param {AttributeString} str - The attribute string identifying the attributes to insert into
- * this map.
- * @param {boolean} [emptyValueIsDelete] - If true and an entry's value is the empty string, the
- * key is removed from this map (if present).
- * @returns {AttributeMap} `this` (for chaining).
- */
- updateFromString(str, emptyValueIsDelete = false) {
- return this.update(attributes.attribsFromString(str, this.pool), emptyValueIsDelete);
- }
-}
-
-module.exports = AttributeMap;
diff --git a/src/static/js/AttributeMap.ts b/src/static/js/AttributeMap.ts
new file mode 100644
index 00000000000..ae39f9a05b9
--- /dev/null
+++ b/src/static/js/AttributeMap.ts
@@ -0,0 +1,83 @@
+import * as attributes from "./attributes.js";
+'use strict';
+/**
+ * A `[key, value]` pair of strings describing a text attribute.
+ *
+ * @typedef {[string, string]} Attribute
+ */
+/**
+ * A concatenated sequence of zero or more attribute identifiers, each one represented by an
+ * asterisk followed by a base-36 encoded attribute number.
+ *
+ * Examples: '', '*0', '*3*j*z*1q'
+ *
+ * @typedef {string} AttributeString
+ */
+/**
+ * Convenience class to convert an Op's attribute string to/from a Map of key, value pairs.
+ */
+class AttributeMap extends Map {
+ pool: any;
+ /**
+ * Converts an attribute string into an AttributeMap.
+ *
+ * @param {AttributeString} str - The attribute string to convert into an AttributeMap.
+ * @param {AttributePool} pool - Attribute pool.
+ * @returns {AttributeMap}
+ */
+ static fromString(str, pool) {
+ return new AttributeMap(pool).updateFromString(str);
+ }
+ /**
+ * @param {AttributePool} pool - Attribute pool.
+ */
+ constructor(pool) {
+ super();
+ /** @public */
+ this.pool = pool;
+ }
+ /**
+ * @param {string} k - Attribute name.
+ * @param {string} v - Attribute value.
+ * @returns {AttributeMap} `this` (for chaining).
+ */
+ set(k, v) {
+ k = k == null ? '' : String(k);
+ v = v == null ? '' : String(v);
+ this.pool.putAttrib([k, v]);
+ return super.set(k, v);
+ }
+ toString() {
+ return attributes.attribsToString(attributes.sort([...this]), this.pool);
+ }
+ /**
+ * @param {Iterable} entries - [key, value] pairs to insert into this map.
+ * @param {boolean} [emptyValueIsDelete] - If true and an entry's value is the empty string, the
+ * key is removed from this map (if present).
+ * @returns {AttributeMap} `this` (for chaining).
+ */
+ update(entries, emptyValueIsDelete = false) {
+ for (let [k, v] of entries) {
+ k = k == null ? '' : String(k);
+ v = v == null ? '' : String(v);
+ if (!v && emptyValueIsDelete) {
+ this.delete(k);
+ }
+ else {
+ this.set(k, v);
+ }
+ }
+ return this;
+ }
+ /**
+ * @param {AttributeString} str - The attribute string identifying the attributes to insert into
+ * this map.
+ * @param {boolean} [emptyValueIsDelete] - If true and an entry's value is the empty string, the
+ * key is removed from this map (if present).
+ * @returns {AttributeMap} `this` (for chaining).
+ */
+ updateFromString(str, emptyValueIsDelete = false) {
+ return this.update(attributes.attribsFromString(str, this.pool), emptyValueIsDelete);
+ }
+}
+export default AttributeMap;
diff --git a/src/static/js/AttributePool.js b/src/static/js/AttributePool.ts
similarity index 98%
rename from src/static/js/AttributePool.js
rename to src/static/js/AttributePool.ts
index ccdd2eb35ca..3f7691db345 100644
--- a/src/static/js/AttributePool.js
+++ b/src/static/js/AttributePool.ts
@@ -54,7 +54,10 @@
* There is one attribute pool per pad, and it includes every current and historical attribute used
* in the pad.
*/
-class AttributePool {
+export class AttributePool {
+ numToAttrib: {};
+ private attribToNum: {};
+ private nextNum: number;
constructor() {
/**
* Maps an attribute identifier to the attribute's `[key, value]` string pair.
diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js
deleted file mode 100644
index 53b3f2c8f09..00000000000
--- a/src/static/js/Changeset.js
+++ /dev/null
@@ -1,2404 +0,0 @@
-'use strict';
-
-/*
- * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * This is the Changeset library copied from the old Etherpad with some modifications
- * to use it in node.js. The original can be found at:
- * https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
- */
-
-const AttributeMap = require('./AttributeMap');
-const AttributePool = require('./AttributePool');
-const attributes = require('./attributes');
-const {padutils} = require('./pad_utils');
-
-/**
- * A `[key, value]` pair of strings describing a text attribute.
- *
- * @typedef {[string, string]} Attribute
- */
-
-/**
- * A concatenated sequence of zero or more attribute identifiers, each one represented by an
- * asterisk followed by a base-36 encoded attribute number.
- *
- * Examples: '', '*0', '*3*j*z*1q'
- *
- * @typedef {string} AttributeString
- */
-
-/**
- * This method is called whenever there is an error in the sync process.
- *
- * @param {string} msg - Just some message
- */
-const error = (msg) => {
- const e = new Error(msg);
- e.easysync = true;
- throw e;
-};
-
-/**
- * Assert that a condition is truthy. If the condition is falsy, the `error` function is called to
- * throw an exception.
- *
- * @param {boolean} b - assertion condition
- * @param {string} msg - error message to include in the exception
- * @type {(b: boolean, msg: string) => asserts b}
- */
-const assert = (b, msg) => {
- if (!b) error(`Failed assertion: ${msg}`);
-};
-
-/**
- * Parses a number from string base 36.
- *
- * @param {string} str - string of the number in base 36
- * @returns {number} number
- */
-exports.parseNum = (str) => parseInt(str, 36);
-
-/**
- * Writes a number in base 36 and puts it in a string.
- *
- * @param {number} num - number
- * @returns {string} string
- */
-exports.numToString = (num) => num.toString(36).toLowerCase();
-
-/**
- * An operation to apply to a shared document.
- */
-class Op {
- /**
- * @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
- */
- constructor(opcode = '') {
- /**
- * The operation's operator:
- * - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
- * document.
- * - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
- * document.
- * - '+': Insert `chars` characters (containing `lines` newlines) at the current position in
- * the document. The inserted characters come from the changeset's character bank.
- * - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an
- * operation.
- *
- * @type {(''|'='|'+'|'-')}
- * @public
- */
- this.opcode = opcode;
-
- /**
- * The number of characters to keep, insert, or delete.
- *
- * @type {number}
- * @public
- */
- this.chars = 0;
-
- /**
- * The number of characters among the `chars` characters that are newlines. If non-zero, the
- * last character must be a newline.
- *
- * @type {number}
- * @public
- */
- this.lines = 0;
-
- /**
- * Identifiers of attributes to apply to the text, represented as a repeated (zero or more)
- * sequence of asterisk followed by a non-negative base-36 (lower-case) integer. For example,
- * '*2*1o' indicates that attributes 2 and 60 apply to the text affected by the operation. The
- * identifiers come from the document's attribute pool.
- *
- * For keep ('=') operations, the attributes are merged with the base text's existing
- * attributes:
- * - A keep op attribute with a non-empty value replaces an existing base text attribute that
- * has the same key.
- * - A keep op attribute with an empty value is interpreted as an instruction to remove an
- * existing base text attribute that has the same key, if one exists.
- *
- * This is the empty string for remove ('-') operations.
- *
- * @type {string}
- * @public
- */
- this.attribs = '';
- }
-
- toString() {
- if (!this.opcode) throw new TypeError('null op');
- if (typeof this.attribs !== 'string') throw new TypeError('attribs must be a string');
- const l = this.lines ? `|${exports.numToString(this.lines)}` : '';
- return this.attribs + l + this.opcode + exports.numToString(this.chars);
- }
-}
-exports.Op = Op;
-
-/**
- * Describes changes to apply to a document. Does not include the attribute pool or the original
- * document.
- *
- * @typedef {object} Changeset
- * @property {number} oldLen - The length of the base document.
- * @property {number} newLen - The length of the document after applying the changeset.
- * @property {string} ops - Serialized sequence of operations. Use `deserializeOps` to parse this
- * string.
- * @property {string} charBank - Characters inserted by insert operations.
- */
-
-/**
- * Returns the required length of the text before changeset can be applied.
- *
- * @param {string} cs - String representation of the Changeset
- * @returns {number} oldLen property
- */
-exports.oldLen = (cs) => exports.unpack(cs).oldLen;
-
-/**
- * Returns the length of the text after changeset is applied.
- *
- * @param {string} cs - String representation of the Changeset
- * @returns {number} newLen property
- */
-exports.newLen = (cs) => exports.unpack(cs).newLen;
-
-/**
- * Parses a string of serialized changeset operations.
- *
- * @param {string} ops - Serialized changeset operations.
- * @yields {Op}
- * @returns {Generator}
- */
-exports.deserializeOps = function* (ops) {
- // TODO: Migrate to String.prototype.matchAll() once there is enough browser support.
- const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
- let match;
- while ((match = regex.exec(ops)) != null) {
- if (match[5] === '$') return; // Start of the insert operation character bank.
- if (match[5] != null) error(`invalid operation: ${ops.slice(regex.lastIndex - 1)}`);
- const op = new Op(match[3]);
- op.lines = exports.parseNum(match[2] || '0');
- op.chars = exports.parseNum(match[4]);
- op.attribs = match[1];
- yield op;
- }
-};
-
-/**
- * Iterator over a changeset's operations.
- *
- * Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
- *
- * @deprecated Use `deserializeOps` instead.
- */
-class OpIter {
- /**
- * @param {string} ops - String encoding the change operations to iterate over.
- */
- constructor(ops) {
- this._gen = exports.deserializeOps(ops);
- this._next = this._gen.next();
- }
-
- /**
- * @returns {boolean} Whether there are any remaining operations.
- */
- hasNext() {
- return !this._next.done;
- }
-
- /**
- * Returns the next operation object and advances the iterator.
- *
- * Note: This does NOT implement the ECMAScript iterator protocol.
- *
- * @param {Op} [opOut] - Deprecated. Operation object to recycle for the return value.
- * @returns {Op} The next operation, or an operation with a falsy `opcode` property if there are
- * no more operations.
- */
- next(opOut = new Op()) {
- if (this.hasNext()) {
- copyOp(this._next.value, opOut);
- this._next = this._gen.next();
- } else {
- clearOp(opOut);
- }
- return opOut;
- }
-}
-
-/**
- * Creates an iterator which decodes string changeset operations.
- *
- * @deprecated Use `deserializeOps` instead.
- * @param {string} opsStr - String encoding of the change operations to perform.
- * @returns {OpIter} Operator iterator object.
- */
-exports.opIterator = (opsStr) => {
- padutils.warnDeprecated(
- 'Changeset.opIterator() is deprecated; use Changeset.deserializeOps() instead');
- return new OpIter(opsStr);
-};
-
-/**
- * Cleans an Op object.
- *
- * @param {Op} op - object to clear
- */
-const clearOp = (op) => {
- op.opcode = '';
- op.chars = 0;
- op.lines = 0;
- op.attribs = '';
-};
-
-/**
- * Creates a new Op object
- *
- * @deprecated Use the `Op` class instead.
- * @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator.
- * @returns {Op}
- */
-exports.newOp = (optOpcode) => {
- padutils.warnDeprecated('Changeset.newOp() is deprecated; use the Changeset.Op class instead');
- return new Op(optOpcode);
-};
-
-/**
- * Copies op1 to op2
- *
- * @param {Op} op1 - src Op
- * @param {Op} [op2] - dest Op. If not given, a new Op is used.
- * @returns {Op} `op2`
- */
-const copyOp = (op1, op2 = new Op()) => Object.assign(op2, op1);
-
-/**
- * Serializes a sequence of Ops.
- *
- * @typedef {object} OpAssembler
- * @property {Function} append -
- * @property {Function} clear -
- * @property {Function} toString -
- */
-
-/**
- * Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
- * pure "keeps". It does not re-order operations.
- *
- * @typedef {object} MergingOpAssembler
- * @property {Function} append -
- * @property {Function} clear -
- * @property {Function} endDocument -
- * @property {Function} toString -
- */
-
-/**
- * Generates operations from the given text and attributes.
- *
- * @param {('-'|'+'|'=')} opcode - The operator to use.
- * @param {string} text - The text to remove/add/keep.
- * @param {(Iterable|AttributeString)} [attribs] - The attributes to insert into the pool
- * (if necessary) and encode. If an attribute string, no checking is performed to ensure that
- * the attributes exist in the pool, are in the canonical order, and contain no duplicate keys.
- * If this is an iterable of attributes, `pool` must be non-null.
- * @param {?AttributePool} pool - Attribute pool. Required if `attribs` is an iterable of
- * attributes, ignored if `attribs` is an attribute string.
- * @yields {Op} One or two ops (depending on the presense of newlines) that cover the given text.
- * @returns {Generator}
- */
-const opsFromText = function* (opcode, text, attribs = '', pool = null) {
- const op = new Op(opcode);
- op.attribs = typeof attribs === 'string'
- ? attribs : new AttributeMap(pool).update(attribs || [], opcode === '+').toString();
- const lastNewlinePos = text.lastIndexOf('\n');
- if (lastNewlinePos < 0) {
- op.chars = text.length;
- op.lines = 0;
- yield op;
- } else {
- op.chars = lastNewlinePos + 1;
- op.lines = text.match(/\n/g).length;
- yield op;
- const op2 = copyOp(op);
- op2.chars = text.length - (lastNewlinePos + 1);
- op2.lines = 0;
- yield op2;
- }
-};
-
-/**
- * Creates an object that allows you to append operations (type Op) and also compresses them if
- * possible. Like MergingOpAssembler, but able to produce conforming exportss from slightly looser
- * input, at the cost of speed. Specifically:
- * - merges consecutive operations that can be merged
- * - strips final "="
- * - ignores 0-length changes
- * - reorders consecutive + and - (which MergingOpAssembler doesn't do)
- *
- * @typedef {object} SmartOpAssembler
- * @property {Function} append -
- * @property {Function} appendOpWithText -
- * @property {Function} clear -
- * @property {Function} endDocument -
- * @property {Function} getLengthChange -
- * @property {Function} toString -
- */
-
-/**
- * Used to check if a Changeset is valid. This function does not check things that require access to
- * the attribute pool (e.g., attribute order) or original text (e.g., newline positions).
- *
- * @param {string} cs - Changeset to check
- * @returns {string} the checked Changeset
- */
-exports.checkRep = (cs) => {
- const unpacked = exports.unpack(cs);
- const oldLen = unpacked.oldLen;
- const newLen = unpacked.newLen;
- const ops = unpacked.ops;
- let charBank = unpacked.charBank;
-
- const assem = exports.smartOpAssembler();
- let oldPos = 0;
- let calcNewLen = 0;
- for (const o of exports.deserializeOps(ops)) {
- switch (o.opcode) {
- case '=':
- oldPos += o.chars;
- calcNewLen += o.chars;
- break;
- case '-':
- oldPos += o.chars;
- assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
- break;
- case '+':
- {
- assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
- const chars = charBank.slice(0, o.chars);
- const nlines = (chars.match(/\n/g) || []).length;
- assert(nlines === o.lines,
- 'Invalid changeset: number of newlines in insert op does not match the charBank');
- assert(o.lines === 0 || chars.endsWith('\n'),
- 'Invalid changeset: multiline insert op does not end with a newline');
- charBank = charBank.slice(o.chars);
- calcNewLen += o.chars;
- assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
- break;
- }
- default:
- assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
- }
- assem.append(o);
- }
- calcNewLen += oldLen - oldPos;
- assert(calcNewLen === newLen, 'Invalid changeset: claimed length does not match actual length');
- assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
- assem.endDocument();
- const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), unpacked.charBank);
- assert(normalized === cs, 'Invalid changeset: not in canonical form');
- return cs;
-};
-
-/**
- * @returns {SmartOpAssembler}
- */
-exports.smartOpAssembler = () => {
- const minusAssem = exports.mergingOpAssembler();
- const plusAssem = exports.mergingOpAssembler();
- const keepAssem = exports.mergingOpAssembler();
- const assem = exports.stringAssembler();
- let lastOpcode = '';
- let lengthChange = 0;
-
- const flushKeeps = () => {
- assem.append(keepAssem.toString());
- keepAssem.clear();
- };
-
- const flushPlusMinus = () => {
- assem.append(minusAssem.toString());
- minusAssem.clear();
- assem.append(plusAssem.toString());
- plusAssem.clear();
- };
-
- const append = (op) => {
- if (!op.opcode) return;
- if (!op.chars) return;
-
- if (op.opcode === '-') {
- if (lastOpcode === '=') {
- flushKeeps();
- }
- minusAssem.append(op);
- lengthChange -= op.chars;
- } else if (op.opcode === '+') {
- if (lastOpcode === '=') {
- flushKeeps();
- }
- plusAssem.append(op);
- lengthChange += op.chars;
- } else if (op.opcode === '=') {
- if (lastOpcode !== '=') {
- flushPlusMinus();
- }
- keepAssem.append(op);
- }
- lastOpcode = op.opcode;
- };
-
- /**
- * Generates operations from the given text and attributes.
- *
- * @deprecated Use `opsFromText` instead.
- * @param {('-'|'+'|'=')} opcode - The operator to use.
- * @param {string} text - The text to remove/add/keep.
- * @param {(string|Iterable)} attribs - The attributes to apply to the operations.
- * @param {?AttributePool} pool - Attribute pool. Only required if `attribs` is an iterable of
- * attribute key, value pairs.
- */
- const appendOpWithText = (opcode, text, attribs, pool) => {
- padutils.warnDeprecated('Changeset.smartOpAssembler().appendOpWithText() is deprecated; ' +
- 'use opsFromText() instead.');
- for (const op of opsFromText(opcode, text, attribs, pool)) append(op);
- };
-
- const toString = () => {
- flushPlusMinus();
- flushKeeps();
- return assem.toString();
- };
-
- const clear = () => {
- minusAssem.clear();
- plusAssem.clear();
- keepAssem.clear();
- assem.clear();
- lengthChange = 0;
- };
-
- const endDocument = () => {
- keepAssem.endDocument();
- };
-
- const getLengthChange = () => lengthChange;
-
- return {
- append,
- toString,
- clear,
- endDocument,
- appendOpWithText,
- getLengthChange,
- };
-};
-
-/**
- * @returns {MergingOpAssembler}
- */
-exports.mergingOpAssembler = () => {
- const assem = exports.opAssembler();
- const bufOp = new Op();
-
- // If we get, for example, insertions [xxx\n,yyy], those don't merge,
- // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
- // This variable stores the length of yyy and any other newline-less
- // ops immediately after it.
- let bufOpAdditionalCharsAfterNewline = 0;
-
- /**
- * @param {boolean} [isEndDocument]
- */
- const flush = (isEndDocument) => {
- if (!bufOp.opcode) return;
- if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
- // final merged keep, leave it implicit
- } else {
- assem.append(bufOp);
- if (bufOpAdditionalCharsAfterNewline) {
- bufOp.chars = bufOpAdditionalCharsAfterNewline;
- bufOp.lines = 0;
- assem.append(bufOp);
- bufOpAdditionalCharsAfterNewline = 0;
- }
- }
- bufOp.opcode = '';
- };
-
- const append = (op) => {
- if (op.chars <= 0) return;
- if (bufOp.opcode === op.opcode && bufOp.attribs === op.attribs) {
- if (op.lines > 0) {
- // bufOp and additional chars are all mergeable into a multi-line op
- bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
- bufOp.lines += op.lines;
- bufOpAdditionalCharsAfterNewline = 0;
- } else if (bufOp.lines === 0) {
- // both bufOp and op are in-line
- bufOp.chars += op.chars;
- } else {
- // append in-line text to multi-line bufOp
- bufOpAdditionalCharsAfterNewline += op.chars;
- }
- } else {
- flush();
- copyOp(op, bufOp);
- }
- };
-
- const endDocument = () => {
- flush(true);
- };
-
- const toString = () => {
- flush();
- return assem.toString();
- };
-
- const clear = () => {
- assem.clear();
- clearOp(bufOp);
- };
- return {
- append,
- toString,
- clear,
- endDocument,
- };
-};
-
-/**
- * @returns {OpAssembler}
- */
-exports.opAssembler = () => {
- let serialized = '';
-
- /**
- * @param {Op} op - Operation to add. Ownership remains with the caller.
- */
- const append = (op) => {
- assert(op instanceof Op, 'argument must be an instance of Op');
- serialized += op.toString();
- };
-
- const toString = () => serialized;
-
- const clear = () => {
- serialized = '';
- };
- return {
- append,
- toString,
- clear,
- };
-};
-
-/**
- * A custom made String Iterator
- *
- * @typedef {object} StringIterator
- * @property {Function} newlines -
- * @property {Function} peek -
- * @property {Function} remaining -
- * @property {Function} skip -
- * @property {Function} take -
- */
-
-/**
- * @param {string} str - String to iterate over
- * @returns {StringIterator}
- */
-exports.stringIterator = (str) => {
- let curIndex = 0;
- // newLines is the number of \n between curIndex and str.length
- let newLines = str.split('\n').length - 1;
- const getnewLines = () => newLines;
-
- const assertRemaining = (n) => {
- assert(n <= remaining(), `!(${n} <= ${remaining()})`);
- };
-
- const take = (n) => {
- assertRemaining(n);
- const s = str.substr(curIndex, n);
- newLines -= s.split('\n').length - 1;
- curIndex += n;
- return s;
- };
-
- const peek = (n) => {
- assertRemaining(n);
- const s = str.substr(curIndex, n);
- return s;
- };
-
- const skip = (n) => {
- assertRemaining(n);
- curIndex += n;
- };
-
- const remaining = () => str.length - curIndex;
- return {
- take,
- skip,
- remaining,
- peek,
- newlines: getnewLines,
- };
-};
-
-/**
- * A custom made StringBuffer
- *
- * @typedef {object} StringAssembler
- * @property {Function} append -
- * @property {Function} toString -
- */
-
-/**
- * @returns {StringAssembler}
- */
-exports.stringAssembler = () => ({
- _str: '',
- clear() { this._str = ''; },
- /**
- * @param {string} x -
- */
- append(x) { this._str += String(x); },
- toString() { return this._str; },
-});
-
-/**
- * @typedef {object} StringArrayLike
- * @property {(i: number) => string} get - Returns the line at index `i`.
- * @property {(number|(() => number))} length - The number of lines, or a method that returns the
- * number of lines.
- * @property {(((start?: number, end?: number) => string[])|undefined)} slice - Like
- * `Array.prototype.slice()`. Optional if the return value of the `removeLines` method is not
- * needed.
- * @property {(i: number, d?: number, ...l: string[]) => any} splice - Like
- * `Array.prototype.splice()`.
- */
-
-/**
- * Class to iterate and modify texts which have several lines. It is used for applying Changesets on
- * arrays of lines.
- *
- * Mutation operations have the same constraints as exports operations with respect to newlines, but
- * not the other additional constraints (i.e. ins/del ordering, forbidden no-ops, non-mergeability,
- * final newline). Can be used to mutate lists of strings where the last char of each string is not
- * actually a newline, but for the purposes of N and L values, the caller should pretend it is, and
- * for things to work right in that case, the input to the `insert` method should be a single line
- * with no newlines.
- */
-class TextLinesMutator {
- /**
- * @param {(string[]|StringArrayLike)} lines - Lines to mutate (in place).
- */
- constructor(lines) {
- this._lines = lines;
- /**
- * this._curSplice holds values that will be passed as arguments to this._lines.splice() to
- * insert, delete, or change lines:
- * - this._curSplice[0] is an index into the this._lines array.
- * - this._curSplice[1] is the number of lines that will be removed from the this._lines array
- * starting at the index.
- * - The other elements represent mutated (changed by ops) lines or new lines (added by ops)
- * to insert at the index.
- *
- * @type {[number, number?, ...string[]?]}
- */
- this._curSplice = [0, 0];
- this._inSplice = false;
- // position in lines after curSplice is applied:
- this._curLine = 0;
- this._curCol = 0;
- // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
- // curLine >= curSplice[0]
- // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
- // curCol == 0
- }
-
- /**
- * Get a line from `lines` at given index.
- *
- * @param {number} idx - an index
- * @returns {string}
- */
- _linesGet(idx) {
- if ('get' in this._lines) {
- return this._lines.get(idx);
- } else {
- return this._lines[idx];
- }
- }
-
- /**
- * Return a slice from `lines`.
- *
- * @param {number} start - the start index
- * @param {number} end - the end index
- * @returns {string[]}
- */
- _linesSlice(start, end) {
- // can be unimplemented if removeLines's return value not needed
- if (this._lines.slice) {
- return this._lines.slice(start, end);
- } else {
- return [];
- }
- }
-
- /**
- * Return the length of `lines`.
- *
- * @returns {number}
- */
- _linesLength() {
- if (typeof this._lines.length === 'number') {
- return this._lines.length;
- } else {
- return this._lines.length();
- }
- }
-
- /**
- * Starts a new splice.
- */
- _enterSplice() {
- this._curSplice[0] = this._curLine;
- this._curSplice[1] = 0;
- // TODO(doc) when is this the case?
- // check all enterSplice calls and changes to curCol
- if (this._curCol > 0) this._putCurLineInSplice();
- this._inSplice = true;
- }
-
- /**
- * Changes the lines array according to the values in curSplice and resets curSplice. Called via
- * close or TODO(doc).
- */
- _leaveSplice() {
- this._lines.splice(...this._curSplice);
- this._curSplice.length = 2;
- this._curSplice[0] = this._curSplice[1] = 0;
- this._inSplice = false;
- }
-
- /**
- * Indicates if curLine is already in the splice. This is necessary because the last element in
- * curSplice is curLine when this line is currently worked on (e.g. when skipping or inserting).
- *
- * @returns {boolean} true if curLine is in splice
- */
- _isCurLineInSplice() {
- // The value of `this._curSplice[1]` does not matter when determining the return value because
- // `this._curLine` refers to the line number *after* the splice is applied (so after those lines
- // are deleted).
- return this._curLine - this._curSplice[0] < this._curSplice.length - 2;
- }
-
- /**
- * Incorporates current line into the splice and marks its old position to be deleted.
- *
- * @returns {number} the index of the added line in curSplice
- */
- _putCurLineInSplice() {
- if (!this._isCurLineInSplice()) {
- this._curSplice.push(this._linesGet(this._curSplice[0] + this._curSplice[1]));
- this._curSplice[1]++;
- }
- // TODO should be the same as this._curSplice.length - 1
- return 2 + this._curLine - this._curSplice[0];
- }
-
- /**
- * It will skip some newlines by putting them into the splice.
- *
- * @param {number} L -
- * @param {boolean} includeInSplice - Indicates that attributes are present.
- */
- skipLines(L, includeInSplice) {
- if (!L) return;
- if (includeInSplice) {
- if (!this._inSplice) this._enterSplice();
- // TODO(doc) should this count the number of characters that are skipped to check?
- for (let i = 0; i < L; i++) {
- this._curCol = 0;
- this._putCurLineInSplice();
- this._curLine++;
- }
- } else {
- if (this._inSplice) {
- if (L > 1) {
- // TODO(doc) figure out why single lines are incorporated into splice instead of ignored
- this._leaveSplice();
- } else {
- this._putCurLineInSplice();
- }
- }
- this._curLine += L;
- this._curCol = 0;
- }
- // tests case foo in remove(), which isn't otherwise covered in current impl
- }
-
- /**
- * Skip some characters. Can contain newlines.
- *
- * @param {number} N - number of characters to skip
- * @param {number} L - number of newlines to skip
- * @param {boolean} includeInSplice - indicates if attributes are present
- */
- skip(N, L, includeInSplice) {
- if (!N) return;
- if (L) {
- this.skipLines(L, includeInSplice);
- } else {
- if (includeInSplice && !this._inSplice) this._enterSplice();
- if (this._inSplice) {
- // although the line is put into splice curLine is not increased, because
- // only some chars are skipped, not the whole line
- this._putCurLineInSplice();
- }
- this._curCol += N;
- }
- }
-
- /**
- * Remove whole lines from lines array.
- *
- * @param {number} L - number of lines to remove
- * @returns {string}
- */
- removeLines(L) {
- if (!L) return '';
- if (!this._inSplice) this._enterSplice();
-
- /**
- * Gets a string of joined lines after the end of the splice.
- *
- * @param {number} k - number of lines
- * @returns {string} joined lines
- */
- const nextKLinesText = (k) => {
- const m = this._curSplice[0] + this._curSplice[1];
- return this._linesSlice(m, m + k).join('');
- };
-
- let removed = '';
- if (this._isCurLineInSplice()) {
- if (this._curCol === 0) {
- removed = this._curSplice[this._curSplice.length - 1];
- this._curSplice.length--;
- removed += nextKLinesText(L - 1);
- this._curSplice[1] += L - 1;
- } else {
- removed = nextKLinesText(L - 1);
- this._curSplice[1] += L - 1;
- const sline = this._curSplice.length - 1;
- removed = this._curSplice[sline].substring(this._curCol) + removed;
- this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) +
- this._linesGet(this._curSplice[0] + this._curSplice[1]);
- this._curSplice[1] += 1;
- }
- } else {
- removed = nextKLinesText(L);
- this._curSplice[1] += L;
- }
- return removed;
- }
-
- /**
- * Remove text from lines array.
- *
- * @param {number} N - characters to delete
- * @param {number} L - lines to delete
- * @returns {string}
- */
- remove(N, L) {
- if (!N) return '';
- if (L) return this.removeLines(L);
- if (!this._inSplice) this._enterSplice();
- // although the line is put into splice, curLine is not increased, because
- // only some chars are removed not the whole line
- const sline = this._putCurLineInSplice();
- const removed = this._curSplice[sline].substring(this._curCol, this._curCol + N);
- this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) +
- this._curSplice[sline].substring(this._curCol + N);
- return removed;
- }
-
- /**
- * Inserts text into lines array.
- *
- * @param {string} text - the text to insert
- * @param {number} L - number of newlines in text
- */
- insert(text, L) {
- if (!text) return;
- if (!this._inSplice) this._enterSplice();
- if (L) {
- const newLines = exports.splitTextLines(text);
- if (this._isCurLineInSplice()) {
- const sline = this._curSplice.length - 1;
- /** @type {string} */
- const theLine = this._curSplice[sline];
- const lineCol = this._curCol;
- // Insert the chars up to `curCol` and the first new line.
- this._curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
- this._curLine++;
- newLines.splice(0, 1);
- // insert the remaining new lines
- this._curSplice.push(...newLines);
- this._curLine += newLines.length;
- // insert the remaining chars from the "old" line (e.g. the line we were in
- // when we started to insert new lines)
- this._curSplice.push(theLine.substring(lineCol));
- this._curCol = 0; // TODO(doc) why is this not set to the length of last line?
- } else {
- this._curSplice.push(...newLines);
- this._curLine += newLines.length;
- }
- } else {
- // There are no additional lines. Although the line is put into splice, curLine is not
- // increased because there may be more chars in the line (newline is not reached).
- const sline = this._putCurLineInSplice();
- if (!this._curSplice[sline]) {
- const err = new Error(
- 'curSplice[sline] not populated, actual curSplice contents is ' +
- `${JSON.stringify(this._curSplice)}. Possibly related to ` +
- 'https://github.com/ether/etherpad-lite/issues/2802');
- console.error(err.stack || err.toString());
- }
- this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) + text +
- this._curSplice[sline].substring(this._curCol);
- this._curCol += text.length;
- }
- }
-
- /**
- * Checks if curLine (the line we are in when curSplice is applied) is the last line in `lines`.
- *
- * @returns {boolean} indicates if there are lines left
- */
- hasMore() {
- let docLines = this._linesLength();
- if (this._inSplice) {
- docLines += this._curSplice.length - 2 - this._curSplice[1];
- }
- return this._curLine < docLines;
- }
-
- /**
- * Closes the splice
- */
- close() {
- if (this._inSplice) this._leaveSplice();
- }
-}
-
-/**
- * Apply operations to other operations.
- *
- * @param {string} in1 - first Op string
- * @param {string} in2 - second Op string
- * @param {Function} func - Callback that applies an operation to another operation. Will be called
- * multiple times depending on the number of operations in `in1` and `in2`. `func` has signature
- * `opOut = f(op1, op2)`:
- * - `op1` is the current operation from `in1`. `func` is expected to mutate `op1` to
- * partially or fully consume it, and MUST set `op1.opcode` to the empty string once `op1`
- * is fully consumed. If `op1` is not fully consumed, `func` will be called again with the
- * same `op1` value. If `op1` is fully consumed, the next call to `func` will be given the
- * next operation from `in1`. If there are no more operations in `in1`, `op1.opcode` will be
- * the empty string.
- * - `op2` is the current operation from `in2`, to apply to `op1`. Has the same consumption
- * and advancement semantics as `op1`.
- * - `opOut` is the result of applying `op2` (before consumption) to `op1` (before
- * consumption). If there is no result (perhaps `op1` and `op2` cancelled each other out),
- * either `opOut` must be nullish or `opOut.opcode` must be the empty string.
- * @returns {string} the integrated changeset
- */
-const applyZip = (in1, in2, func) => {
- const ops1 = exports.deserializeOps(in1);
- const ops2 = exports.deserializeOps(in2);
- let next1 = ops1.next();
- let next2 = ops2.next();
- const assem = exports.smartOpAssembler();
- while (!next1.done || !next2.done) {
- if (!next1.done && !next1.value.opcode) next1 = ops1.next();
- if (!next2.done && !next2.value.opcode) next2 = ops2.next();
- if (next1.value == null) next1.value = new Op();
- if (next2.value == null) next2.value = new Op();
- if (!next1.value.opcode && !next2.value.opcode) break;
- const opOut = func(next1.value, next2.value);
- if (opOut && opOut.opcode) assem.append(opOut);
- }
- assem.endDocument();
- return assem.toString();
-};
-
-/**
- * Parses an encoded changeset.
- *
- * @param {string} cs - The encoded changeset.
- * @returns {Changeset}
- */
-exports.unpack = (cs) => {
- const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
- const headerMatch = headerRegex.exec(cs);
- if ((!headerMatch) || (!headerMatch[0])) error(`Not a changeset: ${cs}`);
- const oldLen = exports.parseNum(headerMatch[1]);
- const changeSign = (headerMatch[2] === '>') ? 1 : -1;
- const changeMag = exports.parseNum(headerMatch[3]);
- const newLen = oldLen + changeSign * changeMag;
- const opsStart = headerMatch[0].length;
- let opsEnd = cs.indexOf('$');
- if (opsEnd < 0) opsEnd = cs.length;
- return {
- oldLen,
- newLen,
- ops: cs.substring(opsStart, opsEnd),
- charBank: cs.substring(opsEnd + 1),
- };
-};
-
-/**
- * Creates an encoded changeset.
- *
- * @param {number} oldLen - The length of the document before applying the changeset.
- * @param {number} newLen - The length of the document after applying the changeset.
- * @param {string} opsStr - Encoded operations to apply to the document.
- * @param {string} bank - Characters for insert operations.
- * @returns {string} The encoded changeset.
- */
-exports.pack = (oldLen, newLen, opsStr, bank) => {
- const lenDiff = newLen - oldLen;
- const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}`
- : `<${exports.numToString(-lenDiff)}`);
- const a = [];
- a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
- return a.join('');
-};
-
-/**
- * Applies a Changeset to a string.
- *
- * @param {string} cs - String encoded Changeset
- * @param {string} str - String to which a Changeset should be applied
- * @returns {string}
- */
-exports.applyToText = (cs, str) => {
- const unpacked = exports.unpack(cs);
- assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
- const bankIter = exports.stringIterator(unpacked.charBank);
- const strIter = exports.stringIterator(str);
- const assem = exports.stringAssembler();
- for (const op of exports.deserializeOps(unpacked.ops)) {
- switch (op.opcode) {
- case '+':
- // op is + and op.lines 0: no newlines must be in op.chars
- // op is + and op.lines >0: op.chars must include op.lines newlines
- if (op.lines !== bankIter.peek(op.chars).split('\n').length - 1) {
- throw new Error(`newline count is wrong in op +; cs:${cs} and text:${str}`);
- }
- assem.append(bankIter.take(op.chars));
- break;
- case '-':
- // op is - and op.lines 0: no newlines must be in the deleted string
- // op is - and op.lines >0: op.lines newlines must be in the deleted string
- if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) {
- throw new Error(`newline count is wrong in op -; cs:${cs} and text:${str}`);
- }
- strIter.skip(op.chars);
- break;
- case '=':
- // op is = and op.lines 0: no newlines must be in the copied string
- // op is = and op.lines >0: op.lines newlines must be in the copied string
- if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) {
- throw new Error(`newline count is wrong in op =; cs:${cs} and text:${str}`);
- }
- assem.append(strIter.take(op.chars));
- break;
- }
- }
- assem.append(strIter.take(strIter.remaining()));
- return assem.toString();
-};
-
-/**
- * Applies a changeset on an array of lines.
- *
- * @param {string} cs - the changeset to apply
- * @param {string[]} lines - The lines to which the changeset needs to be applied
- */
-exports.mutateTextLines = (cs, lines) => {
- const unpacked = exports.unpack(cs);
- const bankIter = exports.stringIterator(unpacked.charBank);
- const mut = new TextLinesMutator(lines);
- for (const op of exports.deserializeOps(unpacked.ops)) {
- switch (op.opcode) {
- case '+':
- mut.insert(bankIter.take(op.chars), op.lines);
- break;
- case '-':
- mut.remove(op.chars, op.lines);
- break;
- case '=':
- mut.skip(op.chars, op.lines, (!!op.attribs));
- break;
- }
- }
- mut.close();
-};
-
-/**
- * Composes two attribute strings (see below) into one.
- *
- * @param {AttributeString} att1 - first attribute string
- * @param {AttributeString} att2 - second attribue string
- * @param {boolean} resultIsMutation -
- * @param {AttributePool} pool - attribute pool
- * @returns {string}
- */
-exports.composeAttributes = (att1, att2, resultIsMutation, pool) => {
- // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
- // Sometimes attribute (key,value) pairs are treated as attribute presence
- // information, while other times they are treated as operations that
- // mutate a set of attributes, and this affects whether an empty value
- // is a deletion or a change.
- // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
- // ([], [(bold, )], true) -> [(bold, )]
- // ([], [(bold, )], false) -> []
- // ([], [(bold, true)], true) -> [(bold, true)]
- // ([], [(bold, true)], false) -> [(bold, true)]
- // ([(bold, true)], [(bold, )], true) -> [(bold, )]
- // ([(bold, true)], [(bold, )], false) -> []
- // pool can be null if att2 has no attributes.
- if ((!att1) && resultIsMutation) {
- // In the case of a mutation (i.e. composing two exportss),
- // an att2 composed with an empy att1 is just att2. If att1
- // is part of an attribution string, then att2 may remove
- // attributes that are already gone, so don't do this optimization.
- return att2;
- }
- if (!att2) return att1;
- return AttributeMap.fromString(att1, pool).updateFromString(att2, !resultIsMutation).toString();
-};
-
-/**
- * Function used as parameter for applyZip to apply a Changeset to an attribute.
- *
- * @param {Op} attOp - The op from the sequence that is being operated on, either an attribution
- * string or the earlier of two exportss being composed.
- * @param {Op} csOp -
- * @param {AttributePool} pool - Can be null if definitely not needed.
- * @returns {Op} The result of applying `csOp` to `attOp`.
- */
-const slicerZipperFunc = (attOp, csOp, pool) => {
- const opOut = new Op();
- if (!attOp.opcode) {
- copyOp(csOp, opOut);
- csOp.opcode = '';
- } else if (!csOp.opcode) {
- copyOp(attOp, opOut);
- attOp.opcode = '';
- } else if (attOp.opcode === '-') {
- copyOp(attOp, opOut);
- attOp.opcode = '';
- } else if (csOp.opcode === '+') {
- copyOp(csOp, opOut);
- csOp.opcode = '';
- } else {
- for (const op of [attOp, csOp]) {
- assert(op.chars >= op.lines, `op has more newlines than chars: ${op.toString()}`);
- }
- assert(
- attOp.chars < csOp.chars ? attOp.lines <= csOp.lines
- : attOp.chars > csOp.chars ? attOp.lines >= csOp.lines
- : attOp.lines === csOp.lines,
- 'line count mismatch when composing changesets A*B; ' +
- `opA: ${attOp.toString()} opB: ${csOp.toString()}`);
- assert(['+', '='].includes(attOp.opcode), `unexpected opcode in op: ${attOp.toString()}`);
- assert(['-', '='].includes(csOp.opcode), `unexpected opcode in op: ${csOp.toString()}`);
- opOut.opcode = {
- '+': {
- '-': '', // The '-' cancels out (some of) the '+', leaving any remainder for the next call.
- '=': '+',
- },
- '=': {
- '-': '-',
- '=': '=',
- },
- }[attOp.opcode][csOp.opcode];
- const [fullyConsumedOp, partiallyConsumedOp] = [attOp, csOp].sort((a, b) => a.chars - b.chars);
- opOut.chars = fullyConsumedOp.chars;
- opOut.lines = fullyConsumedOp.lines;
- opOut.attribs = csOp.opcode === '-'
- // csOp is a remove op and remove ops normally never have any attributes, so this should
- // normally be the empty string. However, padDiff.js adds attributes to remove ops and needs
- // them preserved so they are copied here.
- ? csOp.attribs
- : exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool);
- partiallyConsumedOp.chars -= fullyConsumedOp.chars;
- partiallyConsumedOp.lines -= fullyConsumedOp.lines;
- if (!partiallyConsumedOp.chars) partiallyConsumedOp.opcode = '';
- fullyConsumedOp.opcode = '';
- }
- return opOut;
-};
-
-/**
- * Applies a Changeset to the attribs string of a AText.
- *
- * @param {string} cs - Changeset
- * @param {string} astr - the attribs string of a AText
- * @param {AttributePool} pool - the attibutes pool
- * @returns {string}
- */
-exports.applyToAttribution = (cs, astr, pool) => {
- const unpacked = exports.unpack(cs);
- return applyZip(astr, unpacked.ops, (op1, op2) => slicerZipperFunc(op1, op2, pool));
-};
-
-/**
- * Applies a changeset to an array of attribute lines.
- *
- * @param {string} cs - The encoded changeset.
- * @param {Array} lines - Attribute lines. Modified in place.
- * @param {AttributePool} pool - Attribute pool.
- */
-exports.mutateAttributionLines = (cs, lines, pool) => {
- const unpacked = exports.unpack(cs);
- const csOps = exports.deserializeOps(unpacked.ops);
- let csOpsNext = csOps.next();
- const csBank = unpacked.charBank;
- let csBankIndex = 0;
- // treat the attribution lines as text lines, mutating a line at a time
- const mut = new TextLinesMutator(lines);
-
- /**
- * The Ops in the current line from `lines`.
- *
- * @type {?Generator}
- */
- let lineOps = null;
- let lineOpsNext = null;
-
- const lineOpsHasNext = () => lineOpsNext && !lineOpsNext.done;
- /**
- * Returns false if we are on the last attribute line in `lines` and there is no additional op in
- * that line.
- *
- * @returns {boolean} True if there are more ops to go through.
- */
- const isNextMutOp = () => lineOpsHasNext() || mut.hasMore();
-
- /**
- * @returns {Op} The next Op from `lineIter`. If there are no more Ops, `lineIter` is reset to
- * iterate over the next line, which is consumed from `mut`. If there are no more lines,
- * returns a null Op.
- */
- const nextMutOp = () => {
- if (!lineOpsHasNext() && mut.hasMore()) {
- // There are more attribute lines in `lines` to do AND either we just started so `lineIter` is
- // still null or there are no more ops in current `lineIter`.
- const line = mut.removeLines(1);
- lineOps = exports.deserializeOps(line);
- lineOpsNext = lineOps.next();
- }
- if (!lineOpsHasNext()) return new Op(); // No more ops and no more lines.
- const op = lineOpsNext.value;
- lineOpsNext = lineOps.next();
- return op;
- };
- let lineAssem = null;
-
- /**
- * Appends an op to `lineAssem`. In case `lineAssem` includes one single newline, adds it to the
- * `lines` mutator.
- */
- const outputMutOp = (op) => {
- if (!lineAssem) {
- lineAssem = exports.mergingOpAssembler();
- }
- lineAssem.append(op);
- if (op.lines <= 0) return;
- assert(op.lines === 1, `Can't have op.lines of ${op.lines} in attribution lines`);
- // ship it to the mut
- mut.insert(lineAssem.toString(), 1);
- lineAssem = null;
- };
-
- let csOp = new Op();
- let attOp = new Op();
- while (csOp.opcode || !csOpsNext.done || attOp.opcode || isNextMutOp()) {
- if (!csOp.opcode && !csOpsNext.done) {
- // coOp done, but more ops in cs.
- csOp = csOpsNext.value;
- csOpsNext = csOps.next();
- }
- if (!csOp.opcode && !attOp.opcode && !lineAssem && !lineOpsHasNext()) {
- break; // done
- } else if (csOp.opcode === '=' && csOp.lines > 0 && !csOp.attribs && !attOp.opcode &&
- !lineAssem && !lineOpsHasNext()) {
- // Skip multiple lines without attributes; this is what makes small changes not order of the
- // document size.
- mut.skipLines(csOp.lines);
- csOp.opcode = '';
- } else if (csOp.opcode === '+') {
- const opOut = copyOp(csOp);
- if (csOp.lines > 1) {
- // Copy the first line from `csOp` to `opOut`.
- const firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
- csOp.chars -= firstLineLen;
- csOp.lines--;
- opOut.lines = 1;
- opOut.chars = firstLineLen;
- } else {
- // Either one or no newlines in '+' `csOp`, copy to `opOut` and reset `csOp`.
- csOp.opcode = '';
- }
- outputMutOp(opOut);
- csBankIndex += opOut.chars;
- } else {
- if (!attOp.opcode && isNextMutOp()) attOp = nextMutOp();
- const opOut = slicerZipperFunc(attOp, csOp, pool);
- if (opOut.opcode) outputMutOp(opOut);
- }
- }
-
- assert(!lineAssem, `line assembler not finished:${cs}`);
- mut.close();
-};
-
-/**
- * Joins several Attribution lines.
- *
- * @param {string[]} theAlines - collection of Attribution lines
- * @returns {string} joined Attribution lines
- */
-exports.joinAttributionLines = (theAlines) => {
- const assem = exports.mergingOpAssembler();
- for (const aline of theAlines) {
- for (const op of exports.deserializeOps(aline)) assem.append(op);
- }
- return assem.toString();
-};
-
-exports.splitAttributionLines = (attrOps, text) => {
- const assem = exports.mergingOpAssembler();
- const lines = [];
- let pos = 0;
-
- const appendOp = (op) => {
- assem.append(op);
- if (op.lines > 0) {
- lines.push(assem.toString());
- assem.clear();
- }
- pos += op.chars;
- };
-
- for (const op of exports.deserializeOps(attrOps)) {
- let numChars = op.chars;
- let numLines = op.lines;
- while (numLines > 1) {
- const newlineEnd = text.indexOf('\n', pos) + 1;
- assert(newlineEnd > 0, 'newlineEnd <= 0 in splitAttributionLines');
- op.chars = newlineEnd - pos;
- op.lines = 1;
- appendOp(op);
- numChars -= op.chars;
- numLines -= op.lines;
- }
- if (numLines === 1) {
- op.chars = numChars;
- op.lines = 1;
- }
- appendOp(op);
- }
-
- return lines;
-};
-
-/**
- * Splits text into lines.
- *
- * @param {string} text - text to split
- * @returns {string[]}
- */
-exports.splitTextLines = (text) => text.match(/[^\n]*(?:\n|[^\n]$)/g);
-
-/**
- * Compose two Changesets.
- *
- * @param {string} cs1 - first Changeset
- * @param {string} cs2 - second Changeset
- * @param {AttributePool} pool - Attribs pool
- * @returns {string}
- */
-exports.compose = (cs1, cs2, pool) => {
- const unpacked1 = exports.unpack(cs1);
- const unpacked2 = exports.unpack(cs2);
- const len1 = unpacked1.oldLen;
- const len2 = unpacked1.newLen;
- assert(len2 === unpacked2.oldLen, 'mismatched composition of two changesets');
- const len3 = unpacked2.newLen;
- const bankIter1 = exports.stringIterator(unpacked1.charBank);
- const bankIter2 = exports.stringIterator(unpacked2.charBank);
- const bankAssem = exports.stringAssembler();
-
- const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
- const op1code = op1.opcode;
- const op2code = op2.opcode;
- if (op1code === '+' && op2code === '-') {
- bankIter1.skip(Math.min(op1.chars, op2.chars));
- }
- const opOut = slicerZipperFunc(op1, op2, pool);
- if (opOut.opcode === '+') {
- if (op2code === '+') {
- bankAssem.append(bankIter2.take(opOut.chars));
- } else {
- bankAssem.append(bankIter1.take(opOut.chars));
- }
- }
- return opOut;
- });
-
- return exports.pack(len1, len3, newOps, bankAssem.toString());
-};
-
-/**
- * Returns a function that tests if a string of attributes (e.g. '*3*4') contains a given attribute
- * key,value that is already present in the pool.
- *
- * @param {Attribute} attribPair - `[key, value]` pair of strings.
- * @param {AttributePool} pool - Attribute pool
- * @returns {Function}
- */
-exports.attributeTester = (attribPair, pool) => {
- const never = (attribs) => false;
- if (!pool) return never;
- const attribNum = pool.putAttrib(attribPair, true);
- if (attribNum < 0) return never;
- const re = new RegExp(`\\*${exports.numToString(attribNum)}(?!\\w)`);
- return (attribs) => re.test(attribs);
-};
-
-/**
- * Creates the identity Changeset of length N.
- *
- * @param {number} N - length of the identity changeset
- * @returns {string}
- */
-exports.identity = (N) => exports.pack(N, N, '', '');
-
-/**
- * Creates a Changeset which works on oldFullText and removes text from spliceStart to
- * spliceStart+numRemoved and inserts newText instead. Also gives possibility to add attributes
- * optNewTextAPairs for the new text.
- *
- * @param {string} orig - Original text.
- * @param {number} start - Index into `orig` where characters should be removed and inserted.
- * @param {number} ndel - Number of characters to delete at `start`.
- * @param {string} ins - Text to insert at `start` (after deleting `ndel` characters).
- * @param {string} [attribs] - Optional attributes to apply to the inserted text.
- * @param {AttributePool} [pool] - Attribute pool.
- * @returns {string}
- */
-exports.makeSplice = (orig, start, ndel, ins, attribs, pool) => {
- if (start < 0) throw new RangeError(`start index must be non-negative (is ${start})`);
- if (ndel < 0) throw new RangeError(`characters to delete must be non-negative (is ${ndel})`);
- if (start > orig.length) start = orig.length;
- if (ndel > orig.length - start) ndel = orig.length - start;
- const deleted = orig.substring(start, start + ndel);
- const assem = exports.smartOpAssembler();
- const ops = (function* () {
- yield* opsFromText('=', orig.substring(0, start));
- yield* opsFromText('-', deleted);
- yield* opsFromText('+', ins, attribs, pool);
- })();
- for (const op of ops) assem.append(op);
- assem.endDocument();
- return exports.pack(orig.length, orig.length + ins.length - ndel, assem.toString(), ins);
-};
-
-/**
- * Transforms a changeset into a list of splices in the form [startChar, endChar, newText] meaning
- * replace text from startChar to endChar with newText.
- *
- * @param {string} cs - Changeset
- * @returns {[number, number, string][]}
- */
-const toSplices = (cs) => {
- const unpacked = exports.unpack(cs);
- /** @type {[number, number, string][]} */
- const splices = [];
-
- let oldPos = 0;
- const charIter = exports.stringIterator(unpacked.charBank);
- let inSplice = false;
- for (const op of exports.deserializeOps(unpacked.ops)) {
- if (op.opcode === '=') {
- oldPos += op.chars;
- inSplice = false;
- } else {
- if (!inSplice) {
- splices.push([oldPos, oldPos, '']);
- inSplice = true;
- }
- if (op.opcode === '-') {
- oldPos += op.chars;
- splices[splices.length - 1][1] += op.chars;
- } else if (op.opcode === '+') {
- splices[splices.length - 1][2] += charIter.take(op.chars);
- }
- }
- }
-
- return splices;
-};
-
-/**
- * @param {string} cs -
- * @param {number} startChar -
- * @param {number} endChar -
- * @param {number} insertionsAfter -
- * @returns {[number, number]}
- */
-exports.characterRangeFollow = (cs, startChar, endChar, insertionsAfter) => {
- let newStartChar = startChar;
- let newEndChar = endChar;
- let lengthChangeSoFar = 0;
- for (const splice of toSplices(cs)) {
- const spliceStart = splice[0] + lengthChangeSoFar;
- const spliceEnd = splice[1] + lengthChangeSoFar;
- const newTextLength = splice[2].length;
- const thisLengthChange = newTextLength - (spliceEnd - spliceStart);
-
- if (spliceStart <= newStartChar && spliceEnd >= newEndChar) {
- // splice fully replaces/deletes range
- // (also case that handles insertion at a collapsed selection)
- if (insertionsAfter) {
- newStartChar = newEndChar = spliceStart;
- } else {
- newStartChar = newEndChar = spliceStart + newTextLength;
- }
- } else if (spliceEnd <= newStartChar) {
- // splice is before range
- newStartChar += thisLengthChange;
- newEndChar += thisLengthChange;
- } else if (spliceStart >= newEndChar) {
- // splice is after range
- } else if (spliceStart >= newStartChar && spliceEnd <= newEndChar) {
- // splice is inside range
- newEndChar += thisLengthChange;
- } else if (spliceEnd < newEndChar) {
- // splice overlaps beginning of range
- newStartChar = spliceStart + newTextLength;
- newEndChar += thisLengthChange;
- } else {
- // splice overlaps end of range
- newEndChar = spliceStart;
- }
-
- lengthChangeSoFar += thisLengthChange;
- }
-
- return [newStartChar, newEndChar];
-};
-
-/**
- * Iterate over attributes in a changeset and move them from oldPool to newPool.
- *
- * @param {string} cs - Chageset/attribution string to iterate over
- * @param {AttributePool} oldPool - old attributes pool
- * @param {AttributePool} newPool - new attributes pool
- * @returns {string} the new Changeset
- */
-exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
- // works on exports or attribution string
- let dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- const upToDollar = cs.substring(0, dollarPos);
- const fromDollar = cs.substring(dollarPos);
- // order of attribs stays the same
- return upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => {
- const oldNum = exports.parseNum(a);
- const pair = oldPool.getAttrib(oldNum);
- // The attribute might not be in the old pool if the user is viewing the current revision in the
- // timeslider and text is deleted. See: https://github.com/ether/etherpad-lite/issues/3932
- if (!pair) return '';
- const newNum = newPool.putAttrib(pair);
- return `*${exports.numToString(newNum)}`;
- }) + fromDollar;
-};
-
-/**
- * Create an attribution inserting a text.
- *
- * @param {string} text - text to insert
- * @returns {string}
- */
-exports.makeAttribution = (text) => {
- const assem = exports.smartOpAssembler();
- for (const op of opsFromText('+', text)) assem.append(op);
- return assem.toString();
-};
-
-/**
- * Iterates over attributes in exports, attribution string, or attribs property of an op and runs
- * function func on them.
- *
- * @deprecated Use `attributes.decodeAttribString()` instead.
- * @param {string} cs - changeset
- * @param {Function} func - function to call
- */
-exports.eachAttribNumber = (cs, func) => {
- padutils.warnDeprecated(
- 'Changeset.eachAttribNumber() is deprecated; use attributes.decodeAttribString() instead');
- let dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- const upToDollar = cs.substring(0, dollarPos);
-
- // WARNING: The following cannot be replaced with a call to `attributes.decodeAttribString()`
- // because that function only works on attribute strings, not serialized operations or changesets.
- upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => {
- func(exports.parseNum(a));
- return '';
- });
-};
-
-/**
- * Filter attributes which should remain in a Changeset. Callable on a exports, attribution string,
- * or attribs property of an op, though it may easily create adjacent ops that can be merged.
- *
- * @param {string} cs - changeset to filter
- * @param {Function} filter - fnc which returns true if an attribute X (int) should be kept in the
- * Changeset
- * @returns {string}
- */
-exports.filterAttribNumbers = (cs, filter) => exports.mapAttribNumbers(cs, filter);
-
-/**
- * Does exactly the same as exports.filterAttribNumbers.
- *
- * @param {string} cs -
- * @param {Function} func -
- * @returns {string}
- */
-exports.mapAttribNumbers = (cs, func) => {
- let dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- const upToDollar = cs.substring(0, dollarPos);
-
- const newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, (s, a) => {
- const n = func(exports.parseNum(a));
- if (n === true) {
- return s;
- } else if ((typeof n) === 'number') {
- return `*${exports.numToString(n)}`;
- } else {
- return '';
- }
- });
-
- return newUpToDollar + cs.substring(dollarPos);
-};
-
-/**
- * Represents text with attributes.
- *
- * @typedef {object} AText
- * @property {string} attribs - Serialized sequence of insert operations that cover the text in
- * `text`. These operations describe which parts of the text have what attributes.
- * @property {string} text - The text.
- */
-
-/**
- * Create a Changeset going from Identity to a certain state.
- *
- * @param {string} text - text of the final change
- * @param {string} attribs - optional, operations which insert the text and also puts the right
- * attributes
- * @returns {AText}
- */
-exports.makeAText = (text, attribs) => ({
- text,
- attribs: (attribs || exports.makeAttribution(text)),
-});
-
-/**
- * Apply a Changeset to a AText.
- *
- * @param {string} cs - Changeset to apply
- * @param {AText} atext -
- * @param {AttributePool} pool - Attribute Pool to add to
- * @returns {AText}
- */
-exports.applyToAText = (cs, atext, pool) => ({
- text: exports.applyToText(cs, atext.text),
- attribs: exports.applyToAttribution(cs, atext.attribs, pool),
-});
-
-/**
- * Clones a AText structure.
- *
- * @param {AText} atext -
- * @returns {AText}
- */
-exports.cloneAText = (atext) => {
- if (!atext) error('atext is null');
- return {
- text: atext.text,
- attribs: atext.attribs,
- };
-};
-
-/**
- * Copies a AText structure from atext1 to atext2.
- *
- * @param {AText} atext1 -
- * @param {AText} atext2 -
- */
-exports.copyAText = (atext1, atext2) => {
- atext2.text = atext1.text;
- atext2.attribs = atext1.attribs;
-};
-
-/**
- * Convert AText to a series of operations. Strips final newline.
- *
- * @param {AText} atext - The AText to convert.
- * @yields {Op}
- * @returns {Generator}
- */
-exports.opsFromAText = function* (atext) {
- // intentionally skips last newline char of atext
- let lastOp = null;
- for (const op of exports.deserializeOps(atext.attribs)) {
- if (lastOp != null) yield lastOp;
- lastOp = op;
- }
- if (lastOp == null) return;
- // exclude final newline
- if (lastOp.lines <= 1) {
- lastOp.lines = 0;
- lastOp.chars--;
- } else {
- const nextToLastNewlineEnd = atext.text.lastIndexOf('\n', atext.text.length - 2) + 1;
- const lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
- lastOp.lines--;
- lastOp.chars -= (lastLineLength + 1);
- yield copyOp(lastOp);
- lastOp.lines = 0;
- lastOp.chars = lastLineLength;
- }
- if (lastOp.chars) yield lastOp;
-};
-
-/**
- * Append the set of operations from atext to an assembler.
- *
- * @deprecated Use `opsFromAText` instead.
- * @param {AText} atext -
- * @param assem - Assembler like SmartOpAssembler TODO add desc
- */
-exports.appendATextToAssembler = (atext, assem) => {
- padutils.warnDeprecated(
- 'Changeset.appendATextToAssembler() is deprecated; use Changeset.opsFromAText() instead');
- for (const op of exports.opsFromAText(atext)) assem.append(op);
-};
-
-/**
- * Creates a clone of a Changeset and it's APool.
- *
- * @param {string} cs -
- * @param {AttributePool} pool -
- * @returns {{translated: string, pool: AttributePool}}
- */
-exports.prepareForWire = (cs, pool) => {
- const newPool = new AttributePool();
- const newCs = exports.moveOpsToNewPool(cs, pool, newPool);
- return {
- translated: newCs,
- pool: newPool,
- };
-};
-
-/**
- * Checks if a changeset s the identity changeset.
- *
- * @param {string} cs -
- * @returns {boolean}
- */
-exports.isIdentity = (cs) => {
- const unpacked = exports.unpack(cs);
- return unpacked.ops === '' && unpacked.oldLen === unpacked.newLen;
-};
-
-/**
- * @deprecated Use an AttributeMap instead.
- */
-const attribsAttributeValue = (attribs, key, pool) => {
- if (!attribs) return '';
- for (const [k, v] of attributes.attribsFromString(attribs, pool)) {
- if (k === key) return v;
- }
- return '';
-};
-
-/**
- * Returns all the values of attributes with a certain key in an Op attribs string.
- *
- * @deprecated Use an AttributeMap instead.
- * @param {Op} op - Op
- * @param {string} key - string to search for
- * @param {AttributePool} pool - attribute pool
- * @returns {string}
- */
-exports.opAttributeValue = (op, key, pool) => {
- padutils.warnDeprecated(
- 'Changeset.opAttributeValue() is deprecated; use an AttributeMap instead');
- return attribsAttributeValue(op.attribs, key, pool);
-};
-
-/**
- * Returns all the values of attributes with a certain key in an attribs string.
- *
- * @deprecated Use an AttributeMap instead.
- * @param {AttributeString} attribs - Attribute string
- * @param {string} key - string to search for
- * @param {AttributePool} pool - attribute pool
- * @returns {string}
- */
-exports.attribsAttributeValue = (attribs, key, pool) => {
- padutils.warnDeprecated(
- 'Changeset.attribsAttributeValue() is deprecated; use an AttributeMap instead');
- return attribsAttributeValue(attribs, key, pool);
-};
-
-/**
- * Incrementally builds a Changeset.
- *
- * @typedef {object} Builder
- * @property {Function} insert -
- * @property {Function} keep -
- * @property {Function} keepText -
- * @property {Function} remove -
- * @property {Function} toString -
- */
-
-/**
- * @param {number} oldLen - Old length
- * @returns {Builder}
- */
-exports.builder = (oldLen) => {
- const assem = exports.smartOpAssembler();
- const o = new Op();
- const charBank = exports.stringAssembler();
-
- const self = {
- /**
- * @param {number} N - Number of characters to keep.
- * @param {number} L - Number of newlines among the `N` characters. If positive, the last
- * character must be a newline.
- * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
- * (no pool needed in latter case).
- * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
- * attribute key, value pairs.
- * @returns {Builder} this
- */
- keep: (N, L, attribs, pool) => {
- o.opcode = '=';
- o.attribs = typeof attribs === 'string'
- ? attribs : new AttributeMap(pool).update(attribs || []).toString();
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
-
- /**
- * @param {string} text - Text to keep.
- * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
- * (no pool needed in latter case).
- * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
- * attribute key, value pairs.
- * @returns {Builder} this
- */
- keepText: (text, attribs, pool) => {
- for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
- return self;
- },
-
- /**
- * @param {string} text - Text to insert.
- * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
- * (no pool needed in latter case).
- * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
- * attribute key, value pairs.
- * @returns {Builder} this
- */
- insert: (text, attribs, pool) => {
- for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
- charBank.append(text);
- return self;
- },
-
- /**
- * @param {number} N - Number of characters to remove.
- * @param {number} L - Number of newlines among the `N` characters. If positive, the last
- * character must be a newline.
- * @returns {Builder} this
- */
- remove: (N, L) => {
- o.opcode = '-';
- o.attribs = '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
-
- toString: () => {
- assem.endDocument();
- const newLen = oldLen + assem.getLengthChange();
- return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
- },
- };
-
- return self;
-};
-
-/**
- * Constructs an attribute string from a sequence of attributes.
- *
- * @deprecated Use `AttributeMap.prototype.toString()` or `attributes.attribsToString()` instead.
- * @param {string} opcode - The opcode for the Op that will get the resulting attribute string.
- * @param {?(Iterable|AttributeString)} attribs - The attributes to insert into the pool
- * (if necessary) and encode. If an attribute string, no checking is performed to ensure that
- * the attributes exist in the pool, are in the canonical order, and contain no duplicate keys.
- * If this is an iterable of attributes, `pool` must be non-null.
- * @param {AttributePool} pool - Attribute pool. Required if `attribs` is an iterable of attributes,
- * ignored if `attribs` is an attribute string.
- * @returns {AttributeString}
- */
-exports.makeAttribsString = (opcode, attribs, pool) => {
- padutils.warnDeprecated(
- 'Changeset.makeAttribsString() is deprecated; ' +
- 'use AttributeMap.prototype.toString() or attributes.attribsToString() instead');
- if (!attribs || !['=', '+'].includes(opcode)) return '';
- if (typeof attribs === 'string') return attribs;
- return new AttributeMap(pool).update(attribs, opcode === '+').toString();
-};
-
-/**
- * Like "substring" but on a single-line attribution string.
- */
-exports.subattribution = (astr, start, optEnd) => {
- const attOps = exports.deserializeOps(astr);
- let attOpsNext = attOps.next();
- const assem = exports.smartOpAssembler();
- let attOp = new Op();
- const csOp = new Op();
-
- const doCsOp = () => {
- if (!csOp.chars) return;
- while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
- if (!attOp.opcode) {
- attOp = attOpsNext.value;
- attOpsNext = attOps.next();
- }
- if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars &&
- attOp.lines > 0 && csOp.lines <= 0) {
- csOp.lines++;
- }
- const opOut = slicerZipperFunc(attOp, csOp, null);
- if (opOut.opcode) assem.append(opOut);
- }
- };
-
- csOp.opcode = '-';
- csOp.chars = start;
-
- doCsOp();
-
- if (optEnd === undefined) {
- if (attOp.opcode) {
- assem.append(attOp);
- }
- while (!attOpsNext.done) {
- assem.append(attOpsNext.value);
- attOpsNext = attOps.next();
- }
- } else {
- csOp.opcode = '=';
- csOp.chars = optEnd - start;
- doCsOp();
- }
-
- return assem.toString();
-};
-
-exports.inverse = (cs, lines, alines, pool) => {
- // lines and alines are what the exports is meant to apply to.
- // They may be arrays or objects with .get(i) and .length methods.
- // They include final newlines on lines.
-
- const linesGet = (idx) => {
- if (lines.get) {
- return lines.get(idx);
- } else {
- return lines[idx];
- }
- };
-
- /**
- * @param {number} idx -
- * @returns {string}
- */
- const alinesGet = (idx) => {
- if (alines.get) {
- return alines.get(idx);
- } else {
- return alines[idx];
- }
- };
-
- let curLine = 0;
- let curChar = 0;
- let curLineOps = null;
- let curLineOpsNext = null;
- let curLineOpsLine;
- let curLineNextOp = new Op('+');
-
- const unpacked = exports.unpack(cs);
- const builder = exports.builder(unpacked.newLen);
-
- const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
- if (!curLineOps || curLineOpsLine !== curLine) {
- curLineOps = exports.deserializeOps(alinesGet(curLine));
- curLineOpsNext = curLineOps.next();
- curLineOpsLine = curLine;
- let indexIntoLine = 0;
- while (!curLineOpsNext.done) {
- curLineNextOp = curLineOpsNext.value;
- curLineOpsNext = curLineOps.next();
- if (indexIntoLine + curLineNextOp.chars >= curChar) {
- curLineNextOp.chars -= (curChar - indexIntoLine);
- break;
- }
- indexIntoLine += curLineNextOp.chars;
- }
- }
-
- while (numChars > 0) {
- if (!curLineNextOp.chars && curLineOpsNext.done) {
- curLine++;
- curChar = 0;
- curLineOpsLine = curLine;
- curLineNextOp.chars = 0;
- curLineOps = exports.deserializeOps(alinesGet(curLine));
- curLineOpsNext = curLineOps.next();
- }
- if (!curLineNextOp.chars) {
- if (curLineOpsNext.done) {
- curLineNextOp = new Op();
- } else {
- curLineNextOp = curLineOpsNext.value;
- curLineOpsNext = curLineOps.next();
- }
- }
- const charsToUse = Math.min(numChars, curLineNextOp.chars);
- func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
- curLineNextOp.lines > 0);
- numChars -= charsToUse;
- curLineNextOp.chars -= charsToUse;
- curChar += charsToUse;
- }
-
- if (!curLineNextOp.chars && curLineOpsNext.done) {
- curLine++;
- curChar = 0;
- }
- };
-
- const skip = (N, L) => {
- if (L) {
- curLine += L;
- curChar = 0;
- } else if (curLineOps && curLineOpsLine === curLine) {
- consumeAttribRuns(N, () => {});
- } else {
- curChar += N;
- }
- };
-
- const nextText = (numChars) => {
- let len = 0;
- const assem = exports.stringAssembler();
- const firstString = linesGet(curLine).substring(curChar);
- len += firstString.length;
- assem.append(firstString);
-
- let lineNum = curLine + 1;
- while (len < numChars) {
- const nextString = linesGet(lineNum);
- len += nextString.length;
- assem.append(nextString);
- lineNum++;
- }
-
- return assem.toString().substring(0, numChars);
- };
-
- const cachedStrFunc = (func) => {
- const cache = {};
- return (s) => {
- if (!cache[s]) {
- cache[s] = func(s);
- }
- return cache[s];
- };
- };
-
- for (const csOp of exports.deserializeOps(unpacked.ops)) {
- if (csOp.opcode === '=') {
- if (csOp.attribs) {
- const attribs = AttributeMap.fromString(csOp.attribs, pool);
- const undoBackToAttribs = cachedStrFunc((oldAttribsStr) => {
- const oldAttribs = AttributeMap.fromString(oldAttribsStr, pool);
- const backAttribs = new AttributeMap(pool);
- for (const [key, value] of attribs) {
- const oldValue = oldAttribs.get(key) || '';
- if (oldValue !== value) backAttribs.set(key, oldValue);
- }
- // TODO: backAttribs does not restore removed attributes (it is missing attributes that
- // are in oldAttribs but not in attribs). I don't know if that is intentional.
- return backAttribs.toString();
- });
- consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
- builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
- });
- } else {
- skip(csOp.chars, csOp.lines);
- builder.keep(csOp.chars, csOp.lines);
- }
- } else if (csOp.opcode === '+') {
- builder.remove(csOp.chars, csOp.lines);
- } else if (csOp.opcode === '-') {
- const textBank = nextText(csOp.chars);
- let textBankIndex = 0;
- consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
- builder.insert(textBank.substr(textBankIndex, len), attribs);
- textBankIndex += len;
- });
- }
- }
-
- return exports.checkRep(builder.toString());
-};
-
-// %CLIENT FILE ENDS HERE%
-exports.follow = (cs1, cs2, reverseInsertOrder, pool) => {
- const unpacked1 = exports.unpack(cs1);
- const unpacked2 = exports.unpack(cs2);
- const len1 = unpacked1.oldLen;
- const len2 = unpacked2.oldLen;
- assert(len1 === len2, 'mismatched follow - cannot transform cs1 on top of cs2');
- const chars1 = exports.stringIterator(unpacked1.charBank);
- const chars2 = exports.stringIterator(unpacked2.charBank);
-
- const oldLen = unpacked1.newLen;
- let oldPos = 0;
- let newLen = 0;
-
- const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
-
- const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
- const opOut = new Op();
- if (op1.opcode === '+' || op2.opcode === '+') {
- let whichToDo;
- if (op2.opcode !== '+') {
- whichToDo = 1;
- } else if (op1.opcode !== '+') {
- whichToDo = 2;
- } else {
- // both +
- const firstChar1 = chars1.peek(1);
- const firstChar2 = chars2.peek(1);
- const insertFirst1 = hasInsertFirst(op1.attribs);
- const insertFirst2 = hasInsertFirst(op2.attribs);
- if (insertFirst1 && !insertFirst2) {
- whichToDo = 1;
- } else if (insertFirst2 && !insertFirst1) {
- whichToDo = 2;
- } else if (firstChar1 === '\n' && firstChar2 !== '\n') {
- // insert string that doesn't start with a newline first so as not to break up lines
- whichToDo = 2;
- } else if (firstChar1 !== '\n' && firstChar2 === '\n') {
- whichToDo = 1;
- } else if (reverseInsertOrder) {
- // break symmetry:
- whichToDo = 2;
- } else {
- whichToDo = 1;
- }
- }
- if (whichToDo === 1) {
- chars1.skip(op1.chars);
- opOut.opcode = '=';
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- opOut.attribs = '';
- op1.opcode = '';
- } else {
- // whichToDo == 2
- chars2.skip(op2.chars);
- copyOp(op2, opOut);
- op2.opcode = '';
- }
- } else if (op1.opcode === '-') {
- if (!op2.opcode) {
- op1.opcode = '';
- } else if (op1.chars <= op2.chars) {
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (!op2.chars) {
- op2.opcode = '';
- }
- } else {
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- } else if (op2.opcode === '-') {
- copyOp(op2, opOut);
- if (!op1.opcode) {
- op2.opcode = '';
- } else if (op2.chars <= op1.chars) {
- // delete part or all of a keep
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- if (!op1.chars) {
- op1.opcode = '';
- }
- } else {
- // delete all of a keep, and keep going
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- op2.lines -= op1.lines;
- op2.chars -= op1.chars;
- op1.opcode = '';
- }
- } else if (!op1.opcode) {
- copyOp(op2, opOut);
- op2.opcode = '';
- } else if (!op2.opcode) {
- // @NOTE: Critical bugfix for EPL issue #1625. We do not copy op1 here
- // in order to prevent attributes from leaking into result changesets.
- // copyOp(op1, opOut);
- op1.opcode = '';
- } else {
- // both keeps
- opOut.opcode = '=';
- opOut.attribs = followAttributes(op1.attribs, op2.attribs, pool);
- if (op1.chars <= op2.chars) {
- opOut.chars = op1.chars;
- opOut.lines = op1.lines;
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (!op2.chars) {
- op2.opcode = '';
- }
- } else {
- opOut.chars = op2.chars;
- opOut.lines = op2.lines;
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- }
- switch (opOut.opcode) {
- case '=':
- oldPos += opOut.chars;
- newLen += opOut.chars;
- break;
- case '-':
- oldPos += opOut.chars;
- break;
- case '+':
- newLen += opOut.chars;
- break;
- }
- return opOut;
- });
- newLen += oldLen - oldPos;
-
- return exports.pack(oldLen, newLen, newOps, unpacked2.charBank);
-};
-
-const followAttributes = (att1, att2, pool) => {
- // The merge of two sets of attribute changes to the same text
- // takes the lexically-earlier value if there are two values
- // for the same key. Otherwise, all key/value changes from
- // both attribute sets are taken. This operation is the "follow",
- // so a set of changes is produced that can be applied to att1
- // to produce the merged set.
- if ((!att2) || (!pool)) return '';
- if (!att1) return att2;
- const atts = new Map();
- att2.replace(/\*([0-9a-z]+)/g, (_, a) => {
- const [key, val] = pool.getAttrib(exports.parseNum(a));
- atts.set(key, val);
- return '';
- });
- att1.replace(/\*([0-9a-z]+)/g, (_, a) => {
- const [key, val] = pool.getAttrib(exports.parseNum(a));
- if (atts.has(key) && val <= atts.get(key)) atts.delete(key);
- return '';
- });
- // we've only removed attributes, so they're already sorted
- const buf = exports.stringAssembler();
- for (const att of atts) {
- buf.append('*');
- buf.append(exports.numToString(pool.putAttrib(att)));
- }
- return buf.toString();
-};
-
-exports.exportedForTestingOnly = {
- TextLinesMutator,
- followAttributes,
- toSplices,
-};
diff --git a/src/static/js/Changeset.ts b/src/static/js/Changeset.ts
new file mode 100644
index 00000000000..db29738abcc
--- /dev/null
+++ b/src/static/js/Changeset.ts
@@ -0,0 +1,1976 @@
+import AttributeMap from "./AttributeMap.js";
+import {AttributePool} from "./AttributePool";
+import * as attributes from "./attributes.js";
+import { padutils } from "./pad_utils.js";
+import {CustomError} from "../../node/utils/customError";
+'use strict';
+/**
+ * A `[key, value]` pair of strings describing a text attribute.
+ *
+ * @typedef {[string, string]} Attribute
+ */
+/**
+ * A concatenated sequence of zero or more attribute identifiers, each one represented by an
+ * asterisk followed by a base-36 encoded attribute number.
+ *
+ * Examples: '', '*0', '*3*j*z*1q'
+ *
+ * @typedef {string} AttributeString
+ */
+/**
+ * This method is called whenever there is an error in the sync process.
+ *
+ * @param {string} msg - Just some message
+ */
+const error = (msg) => {
+ const e = new CustomError(msg);
+ e.easysync = true;
+ throw e;
+};
+/**
+ * Assert that a condition is truthy. If the condition is falsy, the `error` function is called to
+ * throw an exception.
+ *
+ * @param {boolean} b - assertion condition
+ * @param {string} msg - error message to include in the exception
+ * @type {(b: boolean, msg: string) => asserts b}
+ */
+const assert = (b, msg) => {
+ if (!b)
+ error(`Failed assertion: ${msg}`);
+};
+/**
+ * An operation to apply to a shared document.
+ */
+class Op {
+ chars: number;
+ opcode: string;
+ lines: number;
+ attribs: string;
+ /**
+ * @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
+ */
+ constructor(opcode = '') {
+ /**
+ * The operation's operator:
+ * - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
+ * document.
+ * - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
+ * document.
+ * - '+': Insert `chars` characters (containing `lines` newlines) at the current position in
+ * the document. The inserted characters come from the changeset's character bank.
+ * - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an
+ * operation.
+ *
+ * @type {(''|'='|'+'|'-')}
+ * @public
+ */
+ this.opcode = opcode;
+ /**
+ * The number of characters to keep, insert, or delete.
+ *
+ * @type {number}
+ * @public
+ */
+ this.chars = 0;
+ /**
+ * The number of characters among the `chars` characters that are newlines. If non-zero, the
+ * last character must be a newline.
+ *
+ * @type {number}
+ * @public
+ */
+ this.lines = 0;
+ /**
+ * Identifiers of attributes to apply to the text, represented as a repeated (zero or more)
+ * sequence of asterisk followed by a non-negative base-36 (lower-case) integer. For example,
+ * '*2*1o' indicates that attributes 2 and 60 apply to the text affected by the operation. The
+ * identifiers come from the document's attribute pool.
+ *
+ * For keep ('=') operations, the attributes are merged with the base text's existing
+ * attributes:
+ * - A keep op attribute with a non-empty value replaces an existing base text attribute that
+ * has the same key.
+ * - A keep op attribute with an empty value is interpreted as an instruction to remove an
+ * existing base text attribute that has the same key, if one exists.
+ *
+ * This is the empty string for remove ('-') operations.
+ *
+ * @type {string}
+ * @public
+ */
+ this.attribs = '';
+ }
+ toString() {
+ if (!this.opcode)
+ throw new TypeError('null op');
+ if (typeof this.attribs !== 'string')
+ throw new TypeError('attribs must be a string');
+ const l = this.lines ? `|${exports.numToString(this.lines)}` : '';
+ return this.attribs + l + this.opcode + exports.numToString(this.chars);
+ }
+}
+/**
+ * Iterator over a changeset's operations.
+ *
+ * Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
+ *
+ * @deprecated Use `deserializeOps` instead.
+ */
+class OpIter {
+ private _gen: any;
+ private _next: any;
+ /**
+ * @param {string} ops - String encoding the change operations to iterate over.
+ */
+ constructor(ops) {
+ this._gen = exports.deserializeOps(ops);
+ this._next = this._gen.next();
+ }
+ /**
+ * @returns {boolean} Whether there are any remaining operations.
+ */
+ hasNext() {
+ return !this._next.done;
+ }
+ /**
+ * Returns the next operation object and advances the iterator.
+ *
+ * Note: This does NOT implement the ECMAScript iterator protocol.
+ *
+ * @param {Op} [opOut] - Deprecated. Operation object to recycle for the return value.
+ * @returns {Op} The next operation, or an operation with a falsy `opcode` property if there are
+ * no more operations.
+ */
+ next(opOut = new Op()) {
+ if (this.hasNext()) {
+ copyOp(this._next.value, opOut);
+ this._next = this._gen.next();
+ }
+ else {
+ clearOp(opOut);
+ }
+ return opOut;
+ }
+}
+/**
+ * Cleans an Op object.
+ *
+ * @param {Op} op - object to clear
+ */
+const clearOp = (op) => {
+ op.opcode = '';
+ op.chars = 0;
+ op.lines = 0;
+ op.attribs = '';
+};
+/**
+ * Copies op1 to op2
+ *
+ * @param {Op} op1 - src Op
+ * @param {Op} [op2] - dest Op. If not given, a new Op is used.
+ * @returns {Op} `op2`
+ */
+const copyOp = (op1, op2 = new Op()) => Object.assign(op2, op1);
+/**
+ * Serializes a sequence of Ops.
+ *
+ * @typedef {object} OpAssembler
+ * @property {Function} append -
+ * @property {Function} clear -
+ * @property {Function} toString -
+ */
+/**
+ * Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
+ * pure "keeps". It does not re-order operations.
+ *
+ * @typedef {object} MergingOpAssembler
+ * @property {Function} append -
+ * @property {Function} clear -
+ * @property {Function} endDocument -
+ * @property {Function} toString -
+ */
+/**
+ * Generates operations from the given text and attributes.
+ *
+ * @param {('-'|'+'|'=')} opcode - The operator to use.
+ * @param {string} text - The text to remove/add/keep.
+ * @param {(Iterable|AttributeString)} [attribs] - The attributes to insert into the pool
+ * (if necessary) and encode. If an attribute string, no checking is performed to ensure that
+ * the attributes exist in the pool, are in the canonical order, and contain no duplicate keys.
+ * If this is an iterable of attributes, `pool` must be non-null.
+ * @param {?AttributePool} pool - Attribute pool. Required if `attribs` is an iterable of
+ * attributes, ignored if `attribs` is an attribute string.
+ * @yields {Op} One or two ops (depending on the presense of newlines) that cover the given text.
+ * @returns {Generator}
+ */
+const opsFromText = function* (opcode, text, attribs = '', pool = null) {
+ const op = new Op(opcode);
+ op.attribs = typeof attribs === 'string'
+ ? attribs : new AttributeMap(pool).update(attribs || [], opcode === '+').toString();
+ const lastNewlinePos = text.lastIndexOf('\n');
+ if (lastNewlinePos < 0) {
+ op.chars = text.length;
+ op.lines = 0;
+ yield op;
+ }
+ else {
+ op.chars = lastNewlinePos + 1;
+ op.lines = text.match(/\n/g).length;
+ yield op;
+ const op2 = copyOp(op);
+ op2.chars = text.length - (lastNewlinePos + 1);
+ op2.lines = 0;
+ yield op2;
+ }
+};
+/**
+ * @typedef {object} StringArrayLike
+ * @property {(i: number) => string} get - Returns the line at index `i`.
+ * @property {(number|(() => number))} length - The number of lines, or a method that returns the
+ * number of lines.
+ * @property {(((start?: number, end?: number) => string[])|undefined)} slice - Like
+ * `Array.prototype.slice()`. Optional if the return value of the `removeLines` method is not
+ * needed.
+ * @property {(i: number, d?: number, ...l: string[]) => any} splice - Like
+ * `Array.prototype.splice()`.
+ */
+/**
+ * Class to iterate and modify texts which have several lines. It is used for applying Changesets on
+ * arrays of lines.
+ *
+ * Mutation operations have the same constraints as exports operations with respect to newlines, but
+ * not the other additional constraints (i.e. ins/del ordering, forbidden no-ops, non-mergeability,
+ * final newline). Can be used to mutate lists of strings where the last char of each string is not
+ * actually a newline, but for the purposes of N and L values, the caller should pretend it is, and
+ * for things to work right in that case, the input to the `insert` method should be a single line
+ * with no newlines.
+ */
+class TextLinesMutator {
+ _lines: any;
+ // FIXME What is this for a type?
+ _curSplice: any[];
+ _inSplice: boolean;
+ _curLine: number;
+ _curCol: number;
+ /**
+ * @param {(string[]|StringArrayLike)} lines - Lines to mutate (in place).
+ */
+ constructor(lines) {
+ this._lines = lines;
+ /**
+ * this._curSplice holds values that will be passed as arguments to this._lines.splice() to
+ * insert, delete, or change lines:
+ * - this._curSplice[0] is an index into the this._lines array.
+ * - this._curSplice[1] is the number of lines that will be removed from the this._lines array
+ * starting at the index.
+ * - The other elements represent mutated (changed by ops) lines or new lines (added by ops)
+ * to insert at the index.
+ *
+ * @type {[number, number?, ...string[]?]}
+ */
+ this._curSplice = [0, 0];
+ this._inSplice = false;
+ // position in lines after curSplice is applied:
+ this._curLine = 0;
+ this._curCol = 0;
+ // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
+ // curLine >= curSplice[0]
+ // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
+ // curCol == 0
+ }
+ /**
+ * Get a line from `lines` at given index.
+ *
+ * @param {number} idx - an index
+ * @returns {string}
+ */
+ _linesGet(idx) {
+ if ('get' in this._lines) {
+ return this._lines.get(idx);
+ }
+ else {
+ return this._lines[idx];
+ }
+ }
+ /**
+ * Return a slice from `lines`.
+ *
+ * @param {number} start - the start index
+ * @param {number} end - the end index
+ * @returns {string[]}
+ */
+ _linesSlice(start, end) {
+ // can be unimplemented if removeLines's return value not needed
+ if (this._lines.slice) {
+ return this._lines.slice(start, end);
+ }
+ else {
+ return [];
+ }
+ }
+ /**
+ * Return the length of `lines`.
+ *
+ * @returns {number}
+ */
+ _linesLength() {
+ if (typeof this._lines.length === 'number') {
+ return this._lines.length;
+ }
+ else {
+ return this._lines.length();
+ }
+ }
+ /**
+ * Starts a new splice.
+ */
+ _enterSplice() {
+ this._curSplice[0] = this._curLine;
+ this._curSplice[1] = 0;
+ // TODO(doc) when is this the case?
+ // check all enterSplice calls and changes to curCol
+ if (this._curCol > 0)
+ this._putCurLineInSplice();
+ this._inSplice = true;
+ }
+ /**
+ * Changes the lines array according to the values in curSplice and resets curSplice. Called via
+ * close or TODO(doc).
+ */
+ _leaveSplice() {
+ this._lines.splice(...this._curSplice);
+ this._curSplice.length = 2;
+ this._curSplice[0] = this._curSplice[1] = 0;
+ this._inSplice = false;
+ }
+ /**
+ * Indicates if curLine is already in the splice. This is necessary because the last element in
+ * curSplice is curLine when this line is currently worked on (e.g. when skipping or inserting).
+ *
+ * @returns {boolean} true if curLine is in splice
+ */
+ _isCurLineInSplice() {
+ // The value of `this._curSplice[1]` does not matter when determining the return value because
+ // `this._curLine` refers to the line number *after* the splice is applied (so after those lines
+ // are deleted).
+ return this._curLine - this._curSplice[0] < this._curSplice.length - 2;
+ }
+ /**
+ * Incorporates current line into the splice and marks its old position to be deleted.
+ *
+ * @returns {number} the index of the added line in curSplice
+ */
+ _putCurLineInSplice() {
+ if (!this._isCurLineInSplice()) {
+ this._curSplice.push(this._linesGet(this._curSplice[0] + this._curSplice[1]));
+ this._curSplice[1]++;
+ }
+ // TODO should be the same as this._curSplice.length - 1
+ return 2 + this._curLine - this._curSplice[0];
+ }
+ /**
+ * It will skip some newlines by putting them into the splice.
+ *
+ * @param {number} L -
+ * @param {boolean} includeInSplice - Indicates that attributes are present.
+ */
+ skipLines(L, includeInSplice?) {
+ if (!L)
+ return;
+ if (includeInSplice) {
+ if (!this._inSplice)
+ this._enterSplice();
+ // TODO(doc) should this count the number of characters that are skipped to check?
+ for (let i = 0; i < L; i++) {
+ this._curCol = 0;
+ this._putCurLineInSplice();
+ this._curLine++;
+ }
+ }
+ else {
+ if (this._inSplice) {
+ if (L > 1) {
+ // TODO(doc) figure out why single lines are incorporated into splice instead of ignored
+ this._leaveSplice();
+ }
+ else {
+ this._putCurLineInSplice();
+ }
+ }
+ this._curLine += L;
+ this._curCol = 0;
+ }
+ // tests case foo in remove(), which isn't otherwise covered in current impl
+ }
+ /**
+ * Skip some characters. Can contain newlines.
+ *
+ * @param {number} N - number of characters to skip
+ * @param {number} L - number of newlines to skip
+ * @param {boolean} includeInSplice - indicates if attributes are present
+ */
+ skip(N, L, includeInSplice) {
+ if (!N)
+ return;
+ if (L) {
+ this.skipLines(L, includeInSplice);
+ }
+ else {
+ if (includeInSplice && !this._inSplice)
+ this._enterSplice();
+ if (this._inSplice) {
+ // although the line is put into splice curLine is not increased, because
+ // only some chars are skipped, not the whole line
+ this._putCurLineInSplice();
+ }
+ this._curCol += N;
+ }
+ }
+ /**
+ * Remove whole lines from lines array.
+ *
+ * @param {number} L - number of lines to remove
+ * @returns {string}
+ */
+ removeLines(L) {
+ if (!L)
+ return '';
+ if (!this._inSplice)
+ this._enterSplice();
+ /**
+ * Gets a string of joined lines after the end of the splice.
+ *
+ * @param {number} k - number of lines
+ * @returns {string} joined lines
+ */
+ const nextKLinesText = (k) => {
+ const m = this._curSplice[0] + this._curSplice[1];
+ return this._linesSlice(m, m + k).join('');
+ };
+ let removed = '';
+ if (this._isCurLineInSplice()) {
+ if (this._curCol === 0) {
+ removed = this._curSplice[this._curSplice.length - 1];
+ this._curSplice.length--;
+ removed += nextKLinesText(L - 1);
+ this._curSplice[1] += L - 1;
+ }
+ else {
+ removed = nextKLinesText(L - 1);
+ this._curSplice[1] += L - 1;
+ const sline = this._curSplice.length - 1;
+ removed = this._curSplice[sline].substring(this._curCol) + removed;
+ this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) +
+ this._linesGet(this._curSplice[0] + this._curSplice[1]);
+ this._curSplice[1] += 1;
+ }
+ }
+ else {
+ removed = nextKLinesText(L);
+ this._curSplice[1] += L;
+ }
+ return removed;
+ }
+ /**
+ * Remove text from lines array.
+ *
+ * @param {number} N - characters to delete
+ * @param {number} L - lines to delete
+ * @returns {string}
+ */
+ remove(N, L) {
+ if (!N)
+ return '';
+ if (L)
+ return this.removeLines(L);
+ if (!this._inSplice)
+ this._enterSplice();
+ // although the line is put into splice, curLine is not increased, because
+ // only some chars are removed not the whole line
+ const sline = this._putCurLineInSplice();
+ const removed = this._curSplice[sline].substring(this._curCol, this._curCol + N);
+ this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) +
+ this._curSplice[sline].substring(this._curCol + N);
+ return removed;
+ }
+ /**
+ * Inserts text into lines array.
+ *
+ * @param {string} text - the text to insert
+ * @param {number} L - number of newlines in text
+ */
+ insert(text, L) {
+ if (!text)
+ return;
+ if (!this._inSplice)
+ this._enterSplice();
+ if (L) {
+ const newLines = exports.splitTextLines(text);
+ if (this._isCurLineInSplice()) {
+ const sline = this._curSplice.length - 1;
+ /** @type {string} */
+ const theLine = this._curSplice[sline];
+ const lineCol = this._curCol;
+ // Insert the chars up to `curCol` and the first new line.
+ this._curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
+ this._curLine++;
+ newLines.splice(0, 1);
+ // insert the remaining new lines
+ this._curSplice.push(...newLines);
+ this._curLine += newLines.length;
+ // insert the remaining chars from the "old" line (e.g. the line we were in
+ // when we started to insert new lines)
+ this._curSplice.push(theLine.substring(lineCol));
+ this._curCol = 0; // TODO(doc) why is this not set to the length of last line?
+ }
+ else {
+ this._curSplice.push(...newLines);
+ this._curLine += newLines.length;
+ }
+ }
+ else {
+ // There are no additional lines. Although the line is put into splice, curLine is not
+ // increased because there may be more chars in the line (newline is not reached).
+ const sline = this._putCurLineInSplice();
+ if (!this._curSplice[sline]) {
+ const err = new Error('curSplice[sline] not populated, actual curSplice contents is ' +
+ `${JSON.stringify(this._curSplice)}. Possibly related to ` +
+ 'https://github.com/ether/etherpad-lite/issues/2802');
+ console.error(err.stack || err.toString());
+ }
+ this._curSplice[sline] = this._curSplice[sline].substring(0, this._curCol) + text +
+ this._curSplice[sline].substring(this._curCol);
+ this._curCol += text.length;
+ }
+ }
+ /**
+ * Checks if curLine (the line we are in when curSplice is applied) is the last line in `lines`.
+ *
+ * @returns {boolean} indicates if there are lines left
+ */
+ hasMore() {
+ let docLines = this._linesLength();
+ if (this._inSplice) {
+ docLines += this._curSplice.length - 2 - this._curSplice[1];
+ }
+ return this._curLine < docLines;
+ }
+ /**
+ * Closes the splice
+ */
+ close() {
+ if (this._inSplice)
+ this._leaveSplice();
+ }
+}
+/**
+ * Apply operations to other operations.
+ *
+ * @param {string} in1 - first Op string
+ * @param {string} in2 - second Op string
+ * @param {Function} func - Callback that applies an operation to another operation. Will be called
+ * multiple times depending on the number of operations in `in1` and `in2`. `func` has signature
+ * `opOut = f(op1, op2)`:
+ * - `op1` is the current operation from `in1`. `func` is expected to mutate `op1` to
+ * partially or fully consume it, and MUST set `op1.opcode` to the empty string once `op1`
+ * is fully consumed. If `op1` is not fully consumed, `func` will be called again with the
+ * same `op1` value. If `op1` is fully consumed, the next call to `func` will be given the
+ * next operation from `in1`. If there are no more operations in `in1`, `op1.opcode` will be
+ * the empty string.
+ * - `op2` is the current operation from `in2`, to apply to `op1`. Has the same consumption
+ * and advancement semantics as `op1`.
+ * - `opOut` is the result of applying `op2` (before consumption) to `op1` (before
+ * consumption). If there is no result (perhaps `op1` and `op2` cancelled each other out),
+ * either `opOut` must be nullish or `opOut.opcode` must be the empty string.
+ * @returns {string} the integrated changeset
+ */
+const applyZip = (in1, in2, func) => {
+ const ops1 = exports.deserializeOps(in1);
+ const ops2 = exports.deserializeOps(in2);
+ let next1 = ops1.next();
+ let next2 = ops2.next();
+ const assem = exports.smartOpAssembler();
+ while (!next1.done || !next2.done) {
+ if (!next1.done && !next1.value.opcode)
+ next1 = ops1.next();
+ if (!next2.done && !next2.value.opcode)
+ next2 = ops2.next();
+ if (next1.value == null)
+ next1.value = new Op();
+ if (next2.value == null)
+ next2.value = new Op();
+ if (!next1.value.opcode && !next2.value.opcode)
+ break;
+ const opOut = func(next1.value, next2.value);
+ if (opOut && opOut.opcode)
+ assem.append(opOut);
+ }
+ assem.endDocument();
+ return assem.toString();
+};
+/**
+ * Function used as parameter for applyZip to apply a Changeset to an attribute.
+ *
+ * @param {Op} attOp - The op from the sequence that is being operated on, either an attribution
+ * string or the earlier of two exportss being composed.
+ * @param {Op} csOp -
+ * @param {AttributePool} pool - Can be null if definitely not needed.
+ * @returns {Op} The result of applying `csOp` to `attOp`.
+ */
+const slicerZipperFunc = (attOp, csOp, pool) => {
+ const opOut = new Op();
+ if (!attOp.opcode) {
+ copyOp(csOp, opOut);
+ csOp.opcode = '';
+ }
+ else if (!csOp.opcode) {
+ copyOp(attOp, opOut);
+ attOp.opcode = '';
+ }
+ else if (attOp.opcode === '-') {
+ copyOp(attOp, opOut);
+ attOp.opcode = '';
+ }
+ else if (csOp.opcode === '+') {
+ copyOp(csOp, opOut);
+ csOp.opcode = '';
+ }
+ else {
+ for (const op of [attOp, csOp]) {
+ assert(op.chars >= op.lines, `op has more newlines than chars: ${op.toString()}`);
+ }
+ assert(attOp.chars < csOp.chars ? attOp.lines <= csOp.lines
+ : attOp.chars > csOp.chars ? attOp.lines >= csOp.lines
+ : attOp.lines === csOp.lines, 'line count mismatch when composing changesets A*B; ' +
+ `opA: ${attOp.toString()} opB: ${csOp.toString()}`);
+ assert(['+', '='].includes(attOp.opcode), `unexpected opcode in op: ${attOp.toString()}`);
+ assert(['-', '='].includes(csOp.opcode), `unexpected opcode in op: ${csOp.toString()}`);
+ opOut.opcode = {
+ '+': {
+ '-': '',
+ '=': '+',
+ },
+ '=': {
+ '-': '-',
+ '=': '=',
+ },
+ }[attOp.opcode][csOp.opcode];
+ const [fullyConsumedOp, partiallyConsumedOp] = [attOp, csOp].sort((a, b) => a.chars - b.chars);
+ opOut.chars = fullyConsumedOp.chars;
+ opOut.lines = fullyConsumedOp.lines;
+ opOut.attribs = csOp.opcode === '-'
+ // csOp is a remove op and remove ops normally never have any attributes, so this should
+ // normally be the empty string. However, padDiff.js adds attributes to remove ops and needs
+ // them preserved so they are copied here.
+ ? csOp.attribs
+ : exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool);
+ partiallyConsumedOp.chars -= fullyConsumedOp.chars;
+ partiallyConsumedOp.lines -= fullyConsumedOp.lines;
+ if (!partiallyConsumedOp.chars)
+ partiallyConsumedOp.opcode = '';
+ fullyConsumedOp.opcode = '';
+ }
+ return opOut;
+};
+/**
+ * Transforms a changeset into a list of splices in the form [startChar, endChar, newText] meaning
+ * replace text from startChar to endChar with newText.
+ *
+ * @param {string} cs - Changeset
+ * @returns {[number, number, string][]}
+ */
+const toSplices = (cs) => {
+ const unpacked = exports.unpack(cs);
+ /** @type {[number, number, string][]} */
+ const splices = [];
+ let oldPos = 0;
+ const charIter = exports.stringIterator(unpacked.charBank);
+ let inSplice = false;
+ for (const op of exports.deserializeOps(unpacked.ops)) {
+ if (op.opcode === '=') {
+ oldPos += op.chars;
+ inSplice = false;
+ }
+ else {
+ if (!inSplice) {
+ splices.push([oldPos, oldPos, '']);
+ inSplice = true;
+ }
+ if (op.opcode === '-') {
+ oldPos += op.chars;
+ splices[splices.length - 1][1] += op.chars;
+ }
+ else if (op.opcode === '+') {
+ splices[splices.length - 1][2] += charIter.take(op.chars);
+ }
+ }
+ }
+ return splices;
+};
+/**
+ * @deprecated Use an AttributeMap instead.
+ */
+const attribsAttributeValue = (attribs, key, pool) => {
+ if (!attribs)
+ return '';
+ for (const [k, v] of attributes.attribsFromString(attribs, pool)) {
+ if (k === key)
+ return v;
+ }
+ return '';
+};
+const followAttributes = (att1, att2, pool) => {
+ // The merge of two sets of attribute changes to the same text
+ // takes the lexically-earlier value if there are two values
+ // for the same key. Otherwise, all key/value changes from
+ // both attribute sets are taken. This operation is the "follow",
+ // so a set of changes is produced that can be applied to att1
+ // to produce the merged set.
+ if ((!att2) || (!pool))
+ return '';
+ if (!att1)
+ return att2;
+ const atts = new Map();
+ att2.replace(/\*([0-9a-z]+)/g, (_, a) => {
+ const [key, val] = pool.getAttrib(exports.parseNum(a));
+ atts.set(key, val);
+ return '';
+ });
+ att1.replace(/\*([0-9a-z]+)/g, (_, a) => {
+ const [key, val] = pool.getAttrib(exports.parseNum(a));
+ if (atts.has(key) && val <= atts.get(key))
+ atts.delete(key);
+ return '';
+ });
+ // we've only removed attributes, so they're already sorted
+ const buf = exports.stringAssembler();
+ for (const att of atts) {
+ buf.append('*');
+ buf.append(exports.numToString(pool.putAttrib(att)));
+ }
+ return buf.toString();
+};
+export const parseNum = (str) => parseInt(str, 36);
+export const numToString = (num) => num.toString(36).toLowerCase();
+export const oldLen = (cs) => exports.unpack(cs).oldLen;
+export const newLen = (cs) => exports.unpack(cs).newLen;
+export const deserializeOps = function* (ops) {
+ // TODO: Migrate to String.prototype.matchAll() once there is enough browser support.
+ const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
+ let match;
+ while ((match = regex.exec(ops)) != null) {
+ if (match[5] === '$')
+ return; // Start of the insert operation character bank.
+ if (match[5] != null)
+ error(`invalid operation: ${ops.slice(regex.lastIndex - 1)}`);
+ const op = new Op(match[3]);
+ op.lines = exports.parseNum(match[2] || '0');
+ op.chars = exports.parseNum(match[4]);
+ op.attribs = match[1];
+ yield op;
+ }
+};
+export const opIterator = (opsStr) => {
+ padutils.warnDeprecated('Changeset.opIterator() is deprecated; use Changeset.deserializeOps() instead');
+ return new OpIter(opsStr);
+};
+export const newOp = (optOpcode) => {
+ padutils.warnDeprecated('Changeset.newOp() is deprecated; use the Changeset.Op class instead');
+ return new Op(optOpcode);
+};
+export const checkRep = (cs) => {
+ const unpacked = exports.unpack(cs);
+ const oldLen = unpacked.oldLen;
+ const newLen = unpacked.newLen;
+ const ops = unpacked.ops;
+ let charBank = unpacked.charBank;
+ const assem = exports.smartOpAssembler();
+ let oldPos = 0;
+ let calcNewLen = 0;
+ for (const o of exports.deserializeOps(ops)) {
+ switch (o.opcode) {
+ case '=':
+ oldPos += o.chars;
+ calcNewLen += o.chars;
+ break;
+ case '-':
+ oldPos += o.chars;
+ assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
+ break;
+ case '+':
+ {
+ assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
+ const chars = charBank.slice(0, o.chars);
+ const nlines = (chars.match(/\n/g) || []).length;
+ assert(nlines === o.lines, 'Invalid changeset: number of newlines in insert op does not match the charBank');
+ assert(o.lines === 0 || chars.endsWith('\n'), 'Invalid changeset: multiline insert op does not end with a newline');
+ charBank = charBank.slice(o.chars);
+ calcNewLen += o.chars;
+ assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
+ break;
+ }
+ default:
+ assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
+ }
+ assem.append(o);
+ }
+ calcNewLen += oldLen - oldPos;
+ assert(calcNewLen === newLen, 'Invalid changeset: claimed length does not match actual length');
+ assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
+ assem.endDocument();
+ const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), unpacked.charBank);
+ assert(normalized === cs, 'Invalid changeset: not in canonical form');
+ return cs;
+};
+export const smartOpAssembler = () => {
+ const minusAssem = exports.mergingOpAssembler();
+ const plusAssem = exports.mergingOpAssembler();
+ const keepAssem = exports.mergingOpAssembler();
+ const assem = exports.stringAssembler();
+ let lastOpcode = '';
+ let lengthChange = 0;
+ const flushKeeps = () => {
+ assem.append(keepAssem.toString());
+ keepAssem.clear();
+ };
+ const flushPlusMinus = () => {
+ assem.append(minusAssem.toString());
+ minusAssem.clear();
+ assem.append(plusAssem.toString());
+ plusAssem.clear();
+ };
+ const append = (op) => {
+ if (!op.opcode)
+ return;
+ if (!op.chars)
+ return;
+ if (op.opcode === '-') {
+ if (lastOpcode === '=') {
+ flushKeeps();
+ }
+ minusAssem.append(op);
+ lengthChange -= op.chars;
+ }
+ else if (op.opcode === '+') {
+ if (lastOpcode === '=') {
+ flushKeeps();
+ }
+ plusAssem.append(op);
+ lengthChange += op.chars;
+ }
+ else if (op.opcode === '=') {
+ if (lastOpcode !== '=') {
+ flushPlusMinus();
+ }
+ keepAssem.append(op);
+ }
+ lastOpcode = op.opcode;
+ };
+ /**
+ * Generates operations from the given text and attributes.
+ *
+ * @deprecated Use `opsFromText` instead.
+ * @param {('-'|'+'|'=')} opcode - The operator to use.
+ * @param {string} text - The text to remove/add/keep.
+ * @param {(string|Iterable)} attribs - The attributes to apply to the operations.
+ * @param {?AttributePool} pool - Attribute pool. Only required if `attribs` is an iterable of
+ * attribute key, value pairs.
+ */
+ const appendOpWithText = (opcode, text, attribs, pool) => {
+ padutils.warnDeprecated('Changeset.smartOpAssembler().appendOpWithText() is deprecated; ' +
+ 'use opsFromText() instead.');
+ for (const op of opsFromText(opcode, text, attribs, pool))
+ append(op);
+ };
+ const toString = () => {
+ flushPlusMinus();
+ flushKeeps();
+ return assem.toString();
+ };
+ const clear = () => {
+ minusAssem.clear();
+ plusAssem.clear();
+ keepAssem.clear();
+ assem.clear();
+ lengthChange = 0;
+ };
+ const endDocument = () => {
+ keepAssem.endDocument();
+ };
+ const getLengthChange = () => lengthChange;
+ return {
+ append,
+ toString,
+ clear,
+ endDocument,
+ appendOpWithText,
+ getLengthChange,
+ };
+};
+export const mergingOpAssembler = () => {
+ const assem = exports.opAssembler();
+ const bufOp = new Op();
+ // If we get, for example, insertions [xxx\n,yyy], those don't merge,
+ // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
+ // This variable stores the length of yyy and any other newline-less
+ // ops immediately after it.
+ let bufOpAdditionalCharsAfterNewline = 0;
+ /**
+ * @param {boolean} [isEndDocument]
+ */
+ const flush = (isEndDocument?: boolean) => {
+ if (!bufOp.opcode)
+ return;
+ if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
+ // final merged keep, leave it implicit
+ }
+ else {
+ assem.append(bufOp);
+ if (bufOpAdditionalCharsAfterNewline) {
+ bufOp.chars = bufOpAdditionalCharsAfterNewline;
+ bufOp.lines = 0;
+ assem.append(bufOp);
+ bufOpAdditionalCharsAfterNewline = 0;
+ }
+ }
+ bufOp.opcode = '';
+ };
+ const append = (op) => {
+ if (op.chars <= 0)
+ return;
+ if (bufOp.opcode === op.opcode && bufOp.attribs === op.attribs) {
+ if (op.lines > 0) {
+ // bufOp and additional chars are all mergeable into a multi-line op
+ bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
+ bufOp.lines += op.lines;
+ bufOpAdditionalCharsAfterNewline = 0;
+ }
+ else if (bufOp.lines === 0) {
+ // both bufOp and op are in-line
+ bufOp.chars += op.chars;
+ }
+ else {
+ // append in-line text to multi-line bufOp
+ bufOpAdditionalCharsAfterNewline += op.chars;
+ }
+ }
+ else {
+ flush();
+ copyOp(op, bufOp);
+ }
+ };
+ const endDocument = () => {
+ flush(true);
+ };
+ const toString = () => {
+ flush();
+ return assem.toString();
+ };
+ const clear = () => {
+ assem.clear();
+ clearOp(bufOp);
+ };
+ return {
+ append,
+ toString,
+ clear,
+ endDocument,
+ };
+};
+export const opAssembler = () => {
+ let serialized = '';
+ /**
+ * @param {Op} op - Operation to add. Ownership remains with the caller.
+ */
+ const append = (op) => {
+ assert(op instanceof Op, 'argument must be an instance of Op');
+ serialized += op.toString();
+ };
+ const toString = () => serialized;
+ const clear = () => {
+ serialized = '';
+ };
+ return {
+ append,
+ toString,
+ clear,
+ };
+};
+export const stringIterator = (str) => {
+ let curIndex = 0;
+ // newLines is the number of \n between curIndex and str.length
+ let newLines = str.split('\n').length - 1;
+ const getnewLines = () => newLines;
+ const assertRemaining = (n) => {
+ assert(n <= remaining(), `!(${n} <= ${remaining()})`);
+ };
+ const take = (n) => {
+ assertRemaining(n);
+ const s = str.substr(curIndex, n);
+ newLines -= s.split('\n').length - 1;
+ curIndex += n;
+ return s;
+ };
+ const peek = (n) => {
+ assertRemaining(n);
+ const s = str.substr(curIndex, n);
+ return s;
+ };
+ const skip = (n) => {
+ assertRemaining(n);
+ curIndex += n;
+ };
+ const remaining = () => str.length - curIndex;
+ return {
+ take,
+ skip,
+ remaining,
+ peek,
+ newlines: getnewLines,
+ };
+};
+export const stringAssembler = () => ({
+ _str: '',
+ clear() { this._str = ''; },
+ /**
+ * @param {string} x -
+ */
+ append(x) { this._str += String(x); },
+ toString() { return this._str; },
+});
+export const unpack = (cs) => {
+ const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
+ const headerMatch = headerRegex.exec(cs);
+ if ((!headerMatch) || (!headerMatch[0]))
+ error(`Not a changeset: ${cs}`);
+ const oldLen = exports.parseNum(headerMatch[1]);
+ const changeSign = (headerMatch[2] === '>') ? 1 : -1;
+ const changeMag = exports.parseNum(headerMatch[3]);
+ const newLen = oldLen + changeSign * changeMag;
+ const opsStart = headerMatch[0].length;
+ let opsEnd = cs.indexOf('$');
+ if (opsEnd < 0)
+ opsEnd = cs.length;
+ return {
+ oldLen,
+ newLen,
+ ops: cs.substring(opsStart, opsEnd),
+ charBank: cs.substring(opsEnd + 1),
+ };
+};
+export const pack = (oldLen, newLen, opsStr, bank) => {
+ const lenDiff = newLen - oldLen;
+ const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}`
+ : `<${exports.numToString(-lenDiff)}`);
+ const a = [];
+ a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
+ return a.join('');
+};
+export const applyToText = (cs, str) => {
+ const unpacked = exports.unpack(cs);
+ assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
+ const bankIter = exports.stringIterator(unpacked.charBank);
+ const strIter = exports.stringIterator(str);
+ const assem = exports.stringAssembler();
+ for (const op of exports.deserializeOps(unpacked.ops)) {
+ switch (op.opcode) {
+ case '+':
+ // op is + and op.lines 0: no newlines must be in op.chars
+ // op is + and op.lines >0: op.chars must include op.lines newlines
+ if (op.lines !== bankIter.peek(op.chars).split('\n').length - 1) {
+ throw new Error(`newline count is wrong in op +; cs:${cs} and text:${str}`);
+ }
+ assem.append(bankIter.take(op.chars));
+ break;
+ case '-':
+ // op is - and op.lines 0: no newlines must be in the deleted string
+ // op is - and op.lines >0: op.lines newlines must be in the deleted string
+ if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) {
+ throw new Error(`newline count is wrong in op -; cs:${cs} and text:${str}`);
+ }
+ strIter.skip(op.chars);
+ break;
+ case '=':
+ // op is = and op.lines 0: no newlines must be in the copied string
+ // op is = and op.lines >0: op.lines newlines must be in the copied string
+ if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) {
+ throw new Error(`newline count is wrong in op =; cs:${cs} and text:${str}`);
+ }
+ assem.append(strIter.take(op.chars));
+ break;
+ }
+ }
+ assem.append(strIter.take(strIter.remaining()));
+ return assem.toString();
+};
+export const mutateTextLines = (cs, lines) => {
+ const unpacked = exports.unpack(cs);
+ const bankIter = exports.stringIterator(unpacked.charBank);
+ const mut = new TextLinesMutator(lines);
+ for (const op of exports.deserializeOps(unpacked.ops)) {
+ switch (op.opcode) {
+ case '+':
+ mut.insert(bankIter.take(op.chars), op.lines);
+ break;
+ case '-':
+ mut.remove(op.chars, op.lines);
+ break;
+ case '=':
+ mut.skip(op.chars, op.lines, (!!op.attribs));
+ break;
+ }
+ }
+ mut.close();
+};
+export const composeAttributes = (att1, att2, resultIsMutation, pool) => {
+ // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
+ // Sometimes attribute (key,value) pairs are treated as attribute presence
+ // information, while other times they are treated as operations that
+ // mutate a set of attributes, and this affects whether an empty value
+ // is a deletion or a change.
+ // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
+ // ([], [(bold, )], true) -> [(bold, )]
+ // ([], [(bold, )], false) -> []
+ // ([], [(bold, true)], true) -> [(bold, true)]
+ // ([], [(bold, true)], false) -> [(bold, true)]
+ // ([(bold, true)], [(bold, )], true) -> [(bold, )]
+ // ([(bold, true)], [(bold, )], false) -> []
+ // pool can be null if att2 has no attributes.
+ if ((!att1) && resultIsMutation) {
+ // In the case of a mutation (i.e. composing two exportss),
+ // an att2 composed with an empy att1 is just att2. If att1
+ // is part of an attribution string, then att2 may remove
+ // attributes that are already gone, so don't do this optimization.
+ return att2;
+ }
+ if (!att2)
+ return att1;
+ return AttributeMap.fromString(att1, pool).updateFromString(att2, !resultIsMutation).toString();
+};
+export const applyToAttribution = (cs, astr, pool) => {
+ const unpacked = exports.unpack(cs);
+ return applyZip(astr, unpacked.ops, (op1, op2) => slicerZipperFunc(op1, op2, pool));
+};
+export const mutateAttributionLines = (cs, lines, pool) => {
+ const unpacked = exports.unpack(cs);
+ const csOps = exports.deserializeOps(unpacked.ops);
+ let csOpsNext = csOps.next();
+ const csBank = unpacked.charBank;
+ let csBankIndex = 0;
+ // treat the attribution lines as text lines, mutating a line at a time
+ const mut = new TextLinesMutator(lines);
+ /**
+ * The Ops in the current line from `lines`.
+ *
+ * @type {?Generator}
+ */
+ let lineOps = null;
+ let lineOpsNext = null;
+ const lineOpsHasNext = () => lineOpsNext && !lineOpsNext.done;
+ /**
+ * Returns false if we are on the last attribute line in `lines` and there is no additional op in
+ * that line.
+ *
+ * @returns {boolean} True if there are more ops to go through.
+ */
+ const isNextMutOp = () => lineOpsHasNext() || mut.hasMore();
+ /**
+ * @returns {Op} The next Op from `lineIter`. If there are no more Ops, `lineIter` is reset to
+ * iterate over the next line, which is consumed from `mut`. If there are no more lines,
+ * returns a null Op.
+ */
+ const nextMutOp = () => {
+ if (!lineOpsHasNext() && mut.hasMore()) {
+ // There are more attribute lines in `lines` to do AND either we just started so `lineIter` is
+ // still null or there are no more ops in current `lineIter`.
+ const line = mut.removeLines(1);
+ lineOps = exports.deserializeOps(line);
+ lineOpsNext = lineOps.next();
+ }
+ if (!lineOpsHasNext())
+ return new Op(); // No more ops and no more lines.
+ const op = lineOpsNext.value;
+ lineOpsNext = lineOps.next();
+ return op;
+ };
+ let lineAssem = null;
+ /**
+ * Appends an op to `lineAssem`. In case `lineAssem` includes one single newline, adds it to the
+ * `lines` mutator.
+ */
+ const outputMutOp = (op) => {
+ if (!lineAssem) {
+ lineAssem = exports.mergingOpAssembler();
+ }
+ lineAssem.append(op);
+ if (op.lines <= 0)
+ return;
+ assert(op.lines === 1, `Can't have op.lines of ${op.lines} in attribution lines`);
+ // ship it to the mut
+ mut.insert(lineAssem.toString(), 1);
+ lineAssem = null;
+ };
+ let csOp = new Op();
+ let attOp = new Op();
+ while (csOp.opcode || !csOpsNext.done || attOp.opcode || isNextMutOp()) {
+ if (!csOp.opcode && !csOpsNext.done) {
+ // coOp done, but more ops in cs.
+ csOp = csOpsNext.value;
+ csOpsNext = csOps.next();
+ }
+ if (!csOp.opcode && !attOp.opcode && !lineAssem && !lineOpsHasNext()) {
+ break; // done
+ }
+ else if (csOp.opcode === '=' && csOp.lines > 0 && !csOp.attribs && !attOp.opcode &&
+ !lineAssem && !lineOpsHasNext()) {
+ // Skip multiple lines without attributes; this is what makes small changes not order of the
+ // document size.
+ mut.skipLines(csOp.lines);
+ csOp.opcode = '';
+ }
+ else if (csOp.opcode === '+') {
+ const opOut = copyOp(csOp);
+ if (csOp.lines > 1) {
+ // Copy the first line from `csOp` to `opOut`.
+ const firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
+ csOp.chars -= firstLineLen;
+ csOp.lines--;
+ opOut.lines = 1;
+ opOut.chars = firstLineLen;
+ }
+ else {
+ // Either one or no newlines in '+' `csOp`, copy to `opOut` and reset `csOp`.
+ csOp.opcode = '';
+ }
+ outputMutOp(opOut);
+ csBankIndex += opOut.chars;
+ }
+ else {
+ if (!attOp.opcode && isNextMutOp())
+ attOp = nextMutOp();
+ const opOut = slicerZipperFunc(attOp, csOp, pool);
+ if (opOut.opcode)
+ outputMutOp(opOut);
+ }
+ }
+ assert(!lineAssem, `line assembler not finished:${cs}`);
+ mut.close();
+};
+export const joinAttributionLines = (theAlines) => {
+ const assem = exports.mergingOpAssembler();
+ for (const aline of theAlines) {
+ for (const op of exports.deserializeOps(aline))
+ assem.append(op);
+ }
+ return assem.toString();
+};
+export const splitAttributionLines = (attrOps, text) => {
+ const assem = exports.mergingOpAssembler();
+ const lines = [];
+ let pos = 0;
+ const appendOp = (op) => {
+ assem.append(op);
+ if (op.lines > 0) {
+ lines.push(assem.toString());
+ assem.clear();
+ }
+ pos += op.chars;
+ };
+ for (const op of exports.deserializeOps(attrOps)) {
+ let numChars = op.chars;
+ let numLines = op.lines;
+ while (numLines > 1) {
+ const newlineEnd = text.indexOf('\n', pos) + 1;
+ assert(newlineEnd > 0, 'newlineEnd <= 0 in splitAttributionLines');
+ op.chars = newlineEnd - pos;
+ op.lines = 1;
+ appendOp(op);
+ numChars -= op.chars;
+ numLines -= op.lines;
+ }
+ if (numLines === 1) {
+ op.chars = numChars;
+ op.lines = 1;
+ }
+ appendOp(op);
+ }
+ return lines;
+};
+export const splitTextLines = (text) => text.match(/[^\n]*(?:\n|[^\n]$)/g);
+export const compose = (cs1, cs2, pool) => {
+ const unpacked1 = exports.unpack(cs1);
+ const unpacked2 = exports.unpack(cs2);
+ const len1 = unpacked1.oldLen;
+ const len2 = unpacked1.newLen;
+ assert(len2 === unpacked2.oldLen, 'mismatched composition of two changesets');
+ const len3 = unpacked2.newLen;
+ const bankIter1 = exports.stringIterator(unpacked1.charBank);
+ const bankIter2 = exports.stringIterator(unpacked2.charBank);
+ const bankAssem = exports.stringAssembler();
+ const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
+ const op1code = op1.opcode;
+ const op2code = op2.opcode;
+ if (op1code === '+' && op2code === '-') {
+ bankIter1.skip(Math.min(op1.chars, op2.chars));
+ }
+ const opOut = slicerZipperFunc(op1, op2, pool);
+ if (opOut.opcode === '+') {
+ if (op2code === '+') {
+ bankAssem.append(bankIter2.take(opOut.chars));
+ }
+ else {
+ bankAssem.append(bankIter1.take(opOut.chars));
+ }
+ }
+ return opOut;
+ });
+ return exports.pack(len1, len3, newOps, bankAssem.toString());
+};
+export const attributeTester = (attribPair, pool) => {
+ const never = (attribs) => false;
+ if (!pool)
+ return never;
+ const attribNum = pool.putAttrib(attribPair, true);
+ if (attribNum < 0)
+ return never;
+ const re = new RegExp(`\\*${exports.numToString(attribNum)}(?!\\w)`);
+ return (attribs) => re.test(attribs);
+};
+export const identity = (N) => exports.pack(N, N, '', '');
+export const makeSplice = (orig, start, ndel, ins, attribs?, pool?) => {
+ if (start < 0)
+ throw new RangeError(`start index must be non-negative (is ${start})`);
+ if (ndel < 0)
+ throw new RangeError(`characters to delete must be non-negative (is ${ndel})`);
+ if (start > orig.length)
+ start = orig.length;
+ if (ndel > orig.length - start)
+ ndel = orig.length - start;
+ const deleted = orig.substring(start, start + ndel);
+ const assem = exports.smartOpAssembler();
+ const ops = (function* () {
+ yield* opsFromText('=', orig.substring(0, start));
+ yield* opsFromText('-', deleted);
+ yield* opsFromText('+', ins, attribs, pool);
+ })();
+ for (const op of ops)
+ assem.append(op);
+ assem.endDocument();
+ return exports.pack(orig.length, orig.length + ins.length - ndel, assem.toString(), ins);
+};
+export const characterRangeFollow = (cs, startChar, endChar, insertionsAfter?) => {
+ let newStartChar = startChar;
+ let newEndChar = endChar;
+ let lengthChangeSoFar = 0;
+ for (const splice of toSplices(cs)) {
+ const spliceStart = splice[0] + lengthChangeSoFar;
+ const spliceEnd = splice[1] + lengthChangeSoFar;
+ const newTextLength = splice[2].length;
+ const thisLengthChange = newTextLength - (spliceEnd - spliceStart);
+ if (spliceStart <= newStartChar && spliceEnd >= newEndChar) {
+ // splice fully replaces/deletes range
+ // (also case that handles insertion at a collapsed selection)
+ if (insertionsAfter) {
+ newStartChar = newEndChar = spliceStart;
+ }
+ else {
+ newStartChar = newEndChar = spliceStart + newTextLength;
+ }
+ }
+ else if (spliceEnd <= newStartChar) {
+ // splice is before range
+ newStartChar += thisLengthChange;
+ newEndChar += thisLengthChange;
+ }
+ else if (spliceStart >= newEndChar) {
+ // splice is after range
+ }
+ else if (spliceStart >= newStartChar && spliceEnd <= newEndChar) {
+ // splice is inside range
+ newEndChar += thisLengthChange;
+ }
+ else if (spliceEnd < newEndChar) {
+ // splice overlaps beginning of range
+ newStartChar = spliceStart + newTextLength;
+ newEndChar += thisLengthChange;
+ }
+ else {
+ // splice overlaps end of range
+ newEndChar = spliceStart;
+ }
+ lengthChangeSoFar += thisLengthChange;
+ }
+ return [newStartChar, newEndChar];
+};
+export const moveOpsToNewPool = (cs, oldPool, newPool) => {
+ // works on exports or attribution string
+ let dollarPos = cs.indexOf('$');
+ if (dollarPos < 0) {
+ dollarPos = cs.length;
+ }
+ const upToDollar = cs.substring(0, dollarPos);
+ const fromDollar = cs.substring(dollarPos);
+ // order of attribs stays the same
+ return upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => {
+ const oldNum = exports.parseNum(a);
+ const pair = oldPool.getAttrib(oldNum);
+ // The attribute might not be in the old pool if the user is viewing the current revision in the
+ // timeslider and text is deleted. See: https://github.com/ether/etherpad-lite/issues/3932
+ if (!pair)
+ return '';
+ const newNum = newPool.putAttrib(pair);
+ return `*${exports.numToString(newNum)}`;
+ }) + fromDollar;
+};
+export const makeAttribution = (text) => {
+ const assem = exports.smartOpAssembler();
+ for (const op of opsFromText('+', text))
+ assem.append(op);
+ return assem.toString();
+};
+export const eachAttribNumber = (cs, func) => {
+ padutils.warnDeprecated('Changeset.eachAttribNumber() is deprecated; use attributes.decodeAttribString() instead');
+ let dollarPos = cs.indexOf('$');
+ if (dollarPos < 0) {
+ dollarPos = cs.length;
+ }
+ const upToDollar = cs.substring(0, dollarPos);
+ // WARNING: The following cannot be replaced with a call to `attributes.decodeAttribString()`
+ // because that function only works on attribute strings, not serialized operations or changesets.
+ upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => {
+ func(exports.parseNum(a));
+ return '';
+ });
+};
+export const filterAttribNumbers = (cs, filter) => exports.mapAttribNumbers(cs, filter);
+export const mapAttribNumbers = (cs, func) => {
+ let dollarPos = cs.indexOf('$');
+ if (dollarPos < 0) {
+ dollarPos = cs.length;
+ }
+ const upToDollar = cs.substring(0, dollarPos);
+ const newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, (s, a) => {
+ const n = func(exports.parseNum(a));
+ if (n === true) {
+ return s;
+ }
+ else if ((typeof n) === 'number') {
+ return `*${exports.numToString(n)}`;
+ }
+ else {
+ return '';
+ }
+ });
+ return newUpToDollar + cs.substring(dollarPos);
+};
+export const makeAText = (text, attribs?) => ({
+ text,
+ attribs: (attribs || exports.makeAttribution(text)),
+});
+export const applyToAText = (cs, atext, pool) => ({
+ text: exports.applyToText(cs, atext.text),
+ attribs: exports.applyToAttribution(cs, atext.attribs, pool),
+});
+export const cloneAText = (atext) => {
+ if (!atext)
+ error('atext is null');
+ return {
+ text: atext.text,
+ attribs: atext.attribs,
+ };
+};
+export const copyAText = (atext1, atext2) => {
+ atext2.text = atext1.text;
+ atext2.attribs = atext1.attribs;
+};
+export const opsFromAText = function* (atext) {
+ // intentionally skips last newline char of atext
+ let lastOp = null;
+ for (const op of exports.deserializeOps(atext.attribs)) {
+ if (lastOp != null)
+ yield lastOp;
+ lastOp = op;
+ }
+ if (lastOp == null)
+ return;
+ // exclude final newline
+ if (lastOp.lines <= 1) {
+ lastOp.lines = 0;
+ lastOp.chars--;
+ }
+ else {
+ const nextToLastNewlineEnd = atext.text.lastIndexOf('\n', atext.text.length - 2) + 1;
+ const lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
+ lastOp.lines--;
+ lastOp.chars -= (lastLineLength + 1);
+ yield copyOp(lastOp);
+ lastOp.lines = 0;
+ lastOp.chars = lastLineLength;
+ }
+ if (lastOp.chars)
+ yield lastOp;
+};
+export const appendATextToAssembler = (atext, assem) => {
+ padutils.warnDeprecated('Changeset.appendATextToAssembler() is deprecated; use Changeset.opsFromAText() instead');
+ for (const op of exports.opsFromAText(atext))
+ assem.append(op);
+};
+export const prepareForWire = (cs, pool) => {
+ const newPool = new AttributePool();
+ const newCs = exports.moveOpsToNewPool(cs, pool, newPool);
+ return {
+ translated: newCs,
+ pool: newPool,
+ };
+};
+export const isIdentity = (cs) => {
+ const unpacked = exports.unpack(cs);
+ return unpacked.ops === '' && unpacked.oldLen === unpacked.newLen;
+};
+export const opAttributeValue = (op, key, pool) => {
+ padutils.warnDeprecated('Changeset.opAttributeValue() is deprecated; use an AttributeMap instead');
+ return attribsAttributeValue(op.attribs, key, pool);
+};
+const attribsAttributeValue$0 = (attribs, key, pool) => {
+ padutils.warnDeprecated('Changeset.attribsAttributeValue() is deprecated; use an AttributeMap instead');
+ return attribsAttributeValue(attribs, key, pool);
+};
+export const builder = (oldLen) => {
+ const assem = exports.smartOpAssembler();
+ const o = new Op();
+ const charBank = exports.stringAssembler();
+ const self = {
+ /**
+ * @param {number} N - Number of characters to keep.
+ * @param {number} L - Number of newlines among the `N` characters. If positive, the last
+ * character must be a newline.
+ * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
+ * (no pool needed in latter case).
+ * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
+ * attribute key, value pairs.
+ * @returns {Builder} this
+ */
+ keep: (N, L, attribs?, pool?) => {
+ o.opcode = '=';
+ o.attribs = typeof attribs === 'string'
+ ? attribs : new AttributeMap(pool).update(attribs || []).toString();
+ o.chars = N;
+ o.lines = (L || 0);
+ assem.append(o);
+ return self;
+ },
+ /**
+ * @param {string} text - Text to keep.
+ * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
+ * (no pool needed in latter case).
+ * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
+ * attribute key, value pairs.
+ * @returns {Builder} this
+ */
+ keepText: (text, attribs?, pool?) => {
+ for (const op of opsFromText('=', text, attribs, pool))
+ assem.append(op);
+ return self;
+ },
+ /**
+ * @param {string} text - Text to insert.
+ * @param {(string|Attribute[])} attribs - Either [[key1,value1],[key2,value2],...] or '*0*1...'
+ * (no pool needed in latter case).
+ * @param {?AttributePool} pool - Attribute pool, only required if `attribs` is a list of
+ * attribute key, value pairs.
+ * @returns {Builder} this
+ */
+ insert: (text, attribs, pool?) => {
+ for (const op of opsFromText('+', text, attribs, pool))
+ assem.append(op);
+ charBank.append(text);
+ return self;
+ },
+ /**
+ * @param {number} N - Number of characters to remove.
+ * @param {number} L - Number of newlines among the `N` characters. If positive, the last
+ * character must be a newline.
+ * @returns {Builder} this
+ */
+ remove: (N, L?) => {
+ o.opcode = '-';
+ o.attribs = '';
+ o.chars = N;
+ o.lines = (L || 0);
+ assem.append(o);
+ return self;
+ },
+ toString: () => {
+ assem.endDocument();
+ const newLen = oldLen + assem.getLengthChange();
+ return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
+ },
+ };
+ return self;
+};
+export const makeAttribsString = (opcode, attribs, pool) => {
+ padutils.warnDeprecated('Changeset.makeAttribsString() is deprecated; ' +
+ 'use AttributeMap.prototype.toString() or attributes.attribsToString() instead');
+ if (!attribs || !['=', '+'].includes(opcode))
+ return '';
+ if (typeof attribs === 'string')
+ return attribs;
+ return new AttributeMap(pool).update(attribs, opcode === '+').toString();
+};
+export const subattribution = (astr, start, optEnd?) => {
+ const attOps = exports.deserializeOps(astr);
+ let attOpsNext = attOps.next();
+ const assem = exports.smartOpAssembler();
+ let attOp = new Op();
+ const csOp = new Op();
+ const doCsOp = () => {
+ if (!csOp.chars)
+ return;
+ while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
+ if (!attOp.opcode) {
+ attOp = attOpsNext.value;
+ attOpsNext = attOps.next();
+ }
+ if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars &&
+ attOp.lines > 0 && csOp.lines <= 0) {
+ csOp.lines++;
+ }
+ const opOut = slicerZipperFunc(attOp, csOp, null);
+ if (opOut.opcode)
+ assem.append(opOut);
+ }
+ };
+ csOp.opcode = '-';
+ csOp.chars = start;
+ doCsOp();
+ if (optEnd === undefined) {
+ if (attOp.opcode) {
+ assem.append(attOp);
+ }
+ while (!attOpsNext.done) {
+ assem.append(attOpsNext.value);
+ attOpsNext = attOps.next();
+ }
+ }
+ else {
+ csOp.opcode = '=';
+ csOp.chars = optEnd - start;
+ doCsOp();
+ }
+ return assem.toString();
+};
+export const inverse = (cs, lines, alines, pool) => {
+ // lines and alines are what the exports is meant to apply to.
+ // They may be arrays or objects with .get(i) and .length methods.
+ // They include final newlines on lines.
+ const linesGet = (idx) => {
+ if (lines.get) {
+ return lines.get(idx);
+ }
+ else {
+ return lines[idx];
+ }
+ };
+ /**
+ * @param {number} idx -
+ * @returns {string}
+ */
+ const alinesGet = (idx) => {
+ if (alines.get) {
+ return alines.get(idx);
+ }
+ else {
+ return alines[idx];
+ }
+ };
+ let curLine = 0;
+ let curChar = 0;
+ let curLineOps = null;
+ let curLineOpsNext = null;
+ let curLineOpsLine;
+ let curLineNextOp = new Op('+');
+ const unpacked = exports.unpack(cs);
+ const builder = exports.builder(unpacked.newLen);
+ const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
+ if (!curLineOps || curLineOpsLine !== curLine) {
+ curLineOps = exports.deserializeOps(alinesGet(curLine));
+ curLineOpsNext = curLineOps.next();
+ curLineOpsLine = curLine;
+ let indexIntoLine = 0;
+ while (!curLineOpsNext.done) {
+ curLineNextOp = curLineOpsNext.value;
+ curLineOpsNext = curLineOps.next();
+ if (indexIntoLine + curLineNextOp.chars >= curChar) {
+ curLineNextOp.chars -= (curChar - indexIntoLine);
+ break;
+ }
+ indexIntoLine += curLineNextOp.chars;
+ }
+ }
+ while (numChars > 0) {
+ if (!curLineNextOp.chars && curLineOpsNext.done) {
+ curLine++;
+ curChar = 0;
+ curLineOpsLine = curLine;
+ curLineNextOp.chars = 0;
+ curLineOps = exports.deserializeOps(alinesGet(curLine));
+ curLineOpsNext = curLineOps.next();
+ }
+ if (!curLineNextOp.chars) {
+ if (curLineOpsNext.done) {
+ curLineNextOp = new Op();
+ }
+ else {
+ curLineNextOp = curLineOpsNext.value;
+ curLineOpsNext = curLineOps.next();
+ }
+ }
+ const charsToUse = Math.min(numChars, curLineNextOp.chars);
+ func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
+ curLineNextOp.lines > 0);
+ numChars -= charsToUse;
+ curLineNextOp.chars -= charsToUse;
+ curChar += charsToUse;
+ }
+ if (!curLineNextOp.chars && curLineOpsNext.done) {
+ curLine++;
+ curChar = 0;
+ }
+ };
+ const skip = (N, L) => {
+ if (L) {
+ curLine += L;
+ curChar = 0;
+ }
+ else if (curLineOps && curLineOpsLine === curLine) {
+ consumeAttribRuns(N, () => { });
+ }
+ else {
+ curChar += N;
+ }
+ };
+ const nextText = (numChars) => {
+ let len = 0;
+ const assem = exports.stringAssembler();
+ const firstString = linesGet(curLine).substring(curChar);
+ len += firstString.length;
+ assem.append(firstString);
+ let lineNum = curLine + 1;
+ while (len < numChars) {
+ const nextString = linesGet(lineNum);
+ len += nextString.length;
+ assem.append(nextString);
+ lineNum++;
+ }
+ return assem.toString().substring(0, numChars);
+ };
+ const cachedStrFunc = (func) => {
+ const cache = {};
+ return (s) => {
+ if (!cache[s]) {
+ cache[s] = func(s);
+ }
+ return cache[s];
+ };
+ };
+ for (const csOp of exports.deserializeOps(unpacked.ops)) {
+ if (csOp.opcode === '=') {
+ if (csOp.attribs) {
+ const attribs = AttributeMap.fromString(csOp.attribs, pool);
+ const undoBackToAttribs = cachedStrFunc((oldAttribsStr) => {
+ const oldAttribs = AttributeMap.fromString(oldAttribsStr, pool);
+ const backAttribs = new AttributeMap(pool);
+ for (const [key, value] of attribs) {
+ const oldValue = oldAttribs.get(key) || '';
+ if (oldValue !== value)
+ backAttribs.set(key, oldValue);
+ }
+ // TODO: backAttribs does not restore removed attributes (it is missing attributes that
+ // are in oldAttribs but not in attribs). I don't know if that is intentional.
+ return backAttribs.toString();
+ });
+ consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
+ builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
+ });
+ }
+ else {
+ skip(csOp.chars, csOp.lines);
+ builder.keep(csOp.chars, csOp.lines);
+ }
+ }
+ else if (csOp.opcode === '+') {
+ builder.remove(csOp.chars, csOp.lines);
+ }
+ else if (csOp.opcode === '-') {
+ const textBank = nextText(csOp.chars);
+ let textBankIndex = 0;
+ consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
+ builder.insert(textBank.substr(textBankIndex, len), attribs);
+ textBankIndex += len;
+ });
+ }
+ }
+ return exports.checkRep(builder.toString());
+};
+export const follow = (cs1, cs2, reverseInsertOrder, pool) => {
+ const unpacked1 = exports.unpack(cs1);
+ const unpacked2 = exports.unpack(cs2);
+ const len1 = unpacked1.oldLen;
+ const len2 = unpacked2.oldLen;
+ assert(len1 === len2, 'mismatched follow - cannot transform cs1 on top of cs2');
+ const chars1 = exports.stringIterator(unpacked1.charBank);
+ const chars2 = exports.stringIterator(unpacked2.charBank);
+ const oldLen = unpacked1.newLen;
+ let oldPos = 0;
+ let newLen = 0;
+ const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
+ const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
+ const opOut = new Op();
+ if (op1.opcode === '+' || op2.opcode === '+') {
+ let whichToDo;
+ if (op2.opcode !== '+') {
+ whichToDo = 1;
+ }
+ else if (op1.opcode !== '+') {
+ whichToDo = 2;
+ }
+ else {
+ // both +
+ const firstChar1 = chars1.peek(1);
+ const firstChar2 = chars2.peek(1);
+ const insertFirst1 = hasInsertFirst(op1.attribs);
+ const insertFirst2 = hasInsertFirst(op2.attribs);
+ if (insertFirst1 && !insertFirst2) {
+ whichToDo = 1;
+ }
+ else if (insertFirst2 && !insertFirst1) {
+ whichToDo = 2;
+ }
+ else if (firstChar1 === '\n' && firstChar2 !== '\n') {
+ // insert string that doesn't start with a newline first so as not to break up lines
+ whichToDo = 2;
+ }
+ else if (firstChar1 !== '\n' && firstChar2 === '\n') {
+ whichToDo = 1;
+ }
+ else if (reverseInsertOrder) {
+ // break symmetry:
+ whichToDo = 2;
+ }
+ else {
+ whichToDo = 1;
+ }
+ }
+ if (whichToDo === 1) {
+ chars1.skip(op1.chars);
+ opOut.opcode = '=';
+ opOut.lines = op1.lines;
+ opOut.chars = op1.chars;
+ opOut.attribs = '';
+ op1.opcode = '';
+ }
+ else {
+ // whichToDo == 2
+ chars2.skip(op2.chars);
+ copyOp(op2, opOut);
+ op2.opcode = '';
+ }
+ }
+ else if (op1.opcode === '-') {
+ if (!op2.opcode) {
+ op1.opcode = '';
+ }
+ else if (op1.chars <= op2.chars) {
+ op2.chars -= op1.chars;
+ op2.lines -= op1.lines;
+ op1.opcode = '';
+ if (!op2.chars) {
+ op2.opcode = '';
+ }
+ }
+ else {
+ op1.chars -= op2.chars;
+ op1.lines -= op2.lines;
+ op2.opcode = '';
+ }
+ }
+ else if (op2.opcode === '-') {
+ copyOp(op2, opOut);
+ if (!op1.opcode) {
+ op2.opcode = '';
+ }
+ else if (op2.chars <= op1.chars) {
+ // delete part or all of a keep
+ op1.chars -= op2.chars;
+ op1.lines -= op2.lines;
+ op2.opcode = '';
+ if (!op1.chars) {
+ op1.opcode = '';
+ }
+ }
+ else {
+ // delete all of a keep, and keep going
+ opOut.lines = op1.lines;
+ opOut.chars = op1.chars;
+ op2.lines -= op1.lines;
+ op2.chars -= op1.chars;
+ op1.opcode = '';
+ }
+ }
+ else if (!op1.opcode) {
+ copyOp(op2, opOut);
+ op2.opcode = '';
+ }
+ else if (!op2.opcode) {
+ // @NOTE: Critical bugfix for EPL issue #1625. We do not copy op1 here
+ // in order to prevent attributes from leaking into result changesets.
+ // copyOp(op1, opOut);
+ op1.opcode = '';
+ }
+ else {
+ // both keeps
+ opOut.opcode = '=';
+ opOut.attribs = followAttributes(op1.attribs, op2.attribs, pool);
+ if (op1.chars <= op2.chars) {
+ opOut.chars = op1.chars;
+ opOut.lines = op1.lines;
+ op2.chars -= op1.chars;
+ op2.lines -= op1.lines;
+ op1.opcode = '';
+ if (!op2.chars) {
+ op2.opcode = '';
+ }
+ }
+ else {
+ opOut.chars = op2.chars;
+ opOut.lines = op2.lines;
+ op1.chars -= op2.chars;
+ op1.lines -= op2.lines;
+ op2.opcode = '';
+ }
+ }
+ switch (opOut.opcode) {
+ case '=':
+ oldPos += opOut.chars;
+ newLen += opOut.chars;
+ break;
+ case '-':
+ oldPos += opOut.chars;
+ break;
+ case '+':
+ newLen += opOut.chars;
+ break;
+ }
+ return opOut;
+ });
+ newLen += oldLen - oldPos;
+ return exports.pack(oldLen, newLen, newOps, unpacked2.charBank);
+};
+export const exportedForTestingOnly = {
+ TextLinesMutator,
+ followAttributes,
+ toSplices,
+};
+export { Op };
+export { attribsAttributeValue$0 as attribsAttributeValue };
diff --git a/src/static/js/ChangesetUtils.js b/src/static/js/ChangesetUtils.js
deleted file mode 100644
index ef2be2ebe07..00000000000
--- a/src/static/js/ChangesetUtils.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-
-/**
- * This module contains several helper Functions to build Changesets
- * based on a SkipList
- */
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-exports.buildRemoveRange = (rep, builder, start, end) => {
- const startLineOffset = rep.lines.offsetOfIndex(start[0]);
- const endLineOffset = rep.lines.offsetOfIndex(end[0]);
-
- if (end[0] > start[0]) {
- builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
- builder.remove(end[1]);
- } else {
- builder.remove(end[1] - start[1]);
- }
-};
-
-exports.buildKeepRange = (rep, builder, start, end, attribs, pool) => {
- const startLineOffset = rep.lines.offsetOfIndex(start[0]);
- const endLineOffset = rep.lines.offsetOfIndex(end[0]);
-
- if (end[0] > start[0]) {
- builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
- builder.keep(end[1], 0, attribs, pool);
- } else {
- builder.keep(end[1] - start[1], 0, attribs, pool);
- }
-};
-
-exports.buildKeepToStartOfRange = (rep, builder, start) => {
- const startLineOffset = rep.lines.offsetOfIndex(start[0]);
-
- builder.keep(startLineOffset, start[0]);
- builder.keep(start[1]);
-};
diff --git a/src/static/js/ChangesetUtils.ts b/src/static/js/ChangesetUtils.ts
new file mode 100644
index 00000000000..a527f1e74f0
--- /dev/null
+++ b/src/static/js/ChangesetUtils.ts
@@ -0,0 +1,28 @@
+'use strict';
+export const buildRemoveRange = (rep, builder, start, end) => {
+ const startLineOffset = rep.lines.offsetOfIndex(start[0]);
+ const endLineOffset = rep.lines.offsetOfIndex(end[0]);
+ if (end[0] > start[0]) {
+ builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
+ builder.remove(end[1]);
+ }
+ else {
+ builder.remove(end[1] - start[1]);
+ }
+};
+export const buildKeepRange = (rep, builder, start, end, attribs?, pool?) => {
+ const startLineOffset = rep.lines.offsetOfIndex(start[0]);
+ const endLineOffset = rep.lines.offsetOfIndex(end[0]);
+ if (end[0] > start[0]) {
+ builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
+ builder.keep(end[1], 0, attribs, pool);
+ }
+ else {
+ builder.keep(end[1] - start[1], 0, attribs, pool);
+ }
+};
+export const buildKeepToStartOfRange = (rep, builder, start) => {
+ const startLineOffset = rep.lines.offsetOfIndex(start[0]);
+ builder.keep(startLineOffset, start[0]);
+ builder.keep(start[1]);
+};
diff --git a/src/static/js/ChatMessage.js b/src/static/js/ChatMessage.js
deleted file mode 100644
index a627f88f9f3..00000000000
--- a/src/static/js/ChatMessage.js
+++ /dev/null
@@ -1,98 +0,0 @@
-'use strict';
-
-const {padutils: {warnDeprecated}} = require('./pad_utils');
-
-/**
- * Represents a chat message stored in the database and transmitted among users. Plugins can extend
- * the object with additional properties.
- *
- * Supports serialization to JSON.
- */
-class ChatMessage {
- static fromObject(obj) {
- // The userId property was renamed to authorId, and userName was renamed to displayName. Accept
- // the old names in case the db record was written by an older version of Etherpad.
- obj = Object.assign({}, obj); // Don't mutate the caller's object.
- if ('userId' in obj && !('authorId' in obj)) obj.authorId = obj.userId;
- delete obj.userId;
- if ('userName' in obj && !('displayName' in obj)) obj.displayName = obj.userName;
- delete obj.userName;
- return Object.assign(new ChatMessage(), obj);
- }
-
- /**
- * @param {?string} [text] - Initial value of the `text` property.
- * @param {?string} [authorId] - Initial value of the `authorId` property.
- * @param {?number} [time] - Initial value of the `time` property.
- */
- constructor(text = null, authorId = null, time = null) {
- /**
- * The raw text of the user's chat message (before any rendering or processing).
- *
- * @type {?string}
- */
- this.text = text;
-
- /**
- * The user's author ID.
- *
- * @type {?string}
- */
- this.authorId = authorId;
-
- /**
- * The message's timestamp, as milliseconds since epoch.
- *
- * @type {?number}
- */
- this.time = time;
-
- /**
- * The user's display name.
- *
- * @type {?string}
- */
- this.displayName = null;
- }
-
- /**
- * Alias of `authorId`, for compatibility with old plugins.
- *
- * @deprecated Use `authorId` instead.
- * @type {string}
- */
- get userId() {
- warnDeprecated('ChatMessage.userId property is deprecated; use .authorId instead');
- return this.authorId;
- }
- set userId(val) {
- warnDeprecated('ChatMessage.userId property is deprecated; use .authorId instead');
- this.authorId = val;
- }
-
- /**
- * Alias of `displayName`, for compatibility with old plugins.
- *
- * @deprecated Use `displayName` instead.
- * @type {string}
- */
- get userName() {
- warnDeprecated('ChatMessage.userName property is deprecated; use .displayName instead');
- return this.displayName;
- }
- set userName(val) {
- warnDeprecated('ChatMessage.userName property is deprecated; use .displayName instead');
- this.displayName = val;
- }
-
- // TODO: Delete this method once users are unlikely to roll back to a version of Etherpad that
- // doesn't support authorId and displayName.
- toJSON() {
- const {authorId, displayName, ...obj} = this;
- obj.userId = authorId;
- obj.userName = displayName;
- return obj;
- }
-}
-
-module.exports = ChatMessage;
diff --git a/src/static/js/ChatMessage.ts b/src/static/js/ChatMessage.ts
new file mode 100644
index 00000000000..3873e00004a
--- /dev/null
+++ b/src/static/js/ChatMessage.ts
@@ -0,0 +1,96 @@
+import { padutils } from "./pad_utils.js";
+'use strict';
+const { padutils: { warnDeprecated } } = { padutils };
+/**
+ * Represents a chat message stored in the database and transmitted among users. Plugins can extend
+ * the object with additional properties.
+ *
+ * Supports serialization to JSON.
+ */
+class ChatMessage {
+ private text: any;
+ authorId: any;
+ private time: any;
+ displayName: null;
+ static fromObject(obj) {
+ // The userId property was renamed to authorId, and userName was renamed to displayName. Accept
+ // the old names in case the db record was written by an older version of Etherpad.
+ obj = Object.assign({}, obj); // Don't mutate the caller's object.
+ if ('userId' in obj && !('authorId' in obj))
+ obj.authorId = obj.userId;
+ delete obj.userId;
+ if ('userName' in obj && !('displayName' in obj))
+ obj.displayName = obj.userName;
+ delete obj.userName;
+ return Object.assign(new ChatMessage(), obj);
+ }
+ /**
+ * @param {?string} [text] - Initial value of the `text` property.
+ * @param {?string} [authorId] - Initial value of the `authorId` property.
+ * @param {?number} [time] - Initial value of the `time` property.
+ */
+ constructor(text = null, authorId = null, time = null) {
+ /**
+ * The raw text of the user's chat message (before any rendering or processing).
+ *
+ * @type {?string}
+ */
+ this.text = text;
+ /**
+ * The user's author ID.
+ *
+ * @type {?string}
+ */
+ this.authorId = authorId;
+ /**
+ * The message's timestamp, as milliseconds since epoch.
+ *
+ * @type {?number}
+ */
+ this.time = time;
+ /**
+ * The user's display name.
+ *
+ * @type {?string}
+ */
+ this.displayName = null;
+ }
+ /**
+ * Alias of `authorId`, for compatibility with old plugins.
+ *
+ * @deprecated Use `authorId` instead.
+ * @type {string}
+ */
+ get userId() {
+ warnDeprecated('ChatMessage.userId property is deprecated; use .authorId instead');
+ return this.authorId;
+ }
+ set userId(val) {
+ warnDeprecated('ChatMessage.userId property is deprecated; use .authorId instead');
+ this.authorId = val;
+ }
+ /**
+ * Alias of `displayName`, for compatibility with old plugins.
+ *
+ * @deprecated Use `displayName` instead.
+ * @type {string}
+ */
+ get userName() {
+ warnDeprecated('ChatMessage.userName property is deprecated; use .displayName instead');
+ return this.displayName;
+ }
+ set userName(val) {
+ warnDeprecated('ChatMessage.userName property is deprecated; use .displayName instead');
+ this.displayName = val;
+ }
+ // TODO: Delete this method once users are unlikely to roll back to a version of Etherpad that
+ // doesn't support authorId and displayName.
+ toJSON() {
+ const { authorId, displayName, ...obj } = this;
+ let objExtendable = obj as any
+ objExtendable.userId = authorId;
+ objExtendable.userName = displayName;
+ return objExtendable;
+ }
+}
+export default ChatMessage;
diff --git a/src/static/js/ace.js b/src/static/js/ace.ts
similarity index 81%
rename from src/static/js/ace.js
rename to src/static/js/ace.ts
index b0a0425702e..310171dd41d 100644
--- a/src/static/js/ace.js
+++ b/src/static/js/ace.ts
@@ -1,49 +1,28 @@
+import * as hooks from "./pluginfw/hooks.js";
+import { makeCSSManager as makeCSSManager$0 } from "./cssmanager.js";
+import * as pluginUtils from "./pluginfw/shared.js";
+import {Ace2EditorInfo, AceDocType} from "../module/Ace2EditorInfo";
+import {clientVars} from "../../node/handler/PadMessageHandler";
+import {CustomElementWithSheet, CustomWindow} from "../module/CustomWindow";
+import {required} from "../../node/eejs";
'use strict';
-/**
- * This code is mostly from the old Etherpad. Please help us to comment this code.
- * This helps other people to understand this code better and helps them to improve it.
- * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
- */
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// requires: top
-// requires: undefined
-
-const hooks = require('./pluginfw/hooks');
-const makeCSSManager = require('./cssmanager').makeCSSManager;
-const pluginUtils = require('./pluginfw/shared');
-
-const debugLog = (...args) => {};
+const makeCSSManager = { makeCSSManager: makeCSSManager$0 }.makeCSSManager;
+const debugLog = (...args) => { };
// The inner and outer iframe's locations are about:blank, so relative URLs are relative to that.
// Firefox and Chrome seem to do what the developer intends if given a relative URL, but Safari
// errors out unless given an absolute URL for a JavaScript-created element.
const absUrl = (url) => new URL(url, window.location.href).href;
-
const eventFired = async (obj, event, cleanups = [], predicate = () => true) => {
if (typeof cleanups === 'function') {
predicate = cleanups;
cleanups = [];
}
- await new Promise((resolve, reject) => {
+ await new Promise((resolve, reject) => {
let cleanup;
const successCb = () => {
- if (!predicate()) return;
+ if (!predicate())
+ return;
debugLog(`Ace2Editor.init() ${event} event on`, obj);
cleanup();
resolve();
@@ -55,7 +34,7 @@ const eventFired = async (obj, event, cleanups = [], predicate = () => true) =>
reject(err);
};
cleanup = () => {
- cleanup = () => {};
+ cleanup = () => { };
obj.removeEventListener(event, successCb);
obj.removeEventListener('error', errorCb);
};
@@ -64,14 +43,13 @@ const eventFired = async (obj, event, cleanups = [], predicate = () => true) =>
obj.addEventListener('error', errorCb);
});
};
-
// Resolves when the frame's document is ready to be mutated. Browsers seem to be quirky about
// iframe ready events so this function throws the kitchen sink at the problem. Maybe one day we'll
// find a concise general solution.
const frameReady = async (frame) => {
// Can't do `const doc = frame.contentDocument;` because Firefox seems to asynchronously replace
// the document object after the frame is first created for some reason. ¯\_(ツ)_/¯
- const doc = () => frame.contentDocument;
+ const doc:AceDocType = () => frame.contentDocument;
const cleanups = [];
try {
await Promise.race([
@@ -81,28 +59,27 @@ const frameReady = async (frame) => {
eventFired(doc(), 'DOMContentLoaded', cleanups),
eventFired(doc(), 'readystatechange', cleanups, () => doc.readyState === 'complete'),
]);
- } finally {
- for (const cleanup of cleanups) cleanup();
+ }
+ finally {
+ for (const cleanup of cleanups)
+ cleanup();
}
};
-
const Ace2Editor = function () {
- let info = {editor: this};
+ let info:Ace2EditorInfo = { editor: this };
let loaded = false;
-
let actionsPendingInit = [];
-
const pendingInit = (func) => function (...args) {
const action = () => func.apply(this, args);
- if (loaded) return action();
+ if (loaded)
+ return action();
actionsPendingInit.push(action);
};
-
const doActionsPendingInit = () => {
- for (const fn of actionsPendingInit) fn();
+ for (const fn of actionsPendingInit)
+ fn();
actionsPendingInit = [];
};
-
// The following functions (prefixed by 'ace_') are exposed by editor, but
// execution is delayed until init is complete
const aceFunctionsPendingInit = [
@@ -124,7 +101,6 @@ const Ace2Editor = function () {
'execCommand',
'replaceRange',
];
-
for (const fnName of aceFunctionsPendingInit) {
// Note: info[`ace_${fnName}`] does not exist yet, so it can't be passed directly to
// pendingInit(). A simple wrapper is used to defer the info[`ace_${fnName}`] lookup until
@@ -133,12 +109,9 @@ const Ace2Editor = function () {
info[`ace_${fnName}`].apply(this, args);
});
}
-
this.exportText = () => loaded ? info.ace_exportText() : '(awaiting init)\n';
-
this.getInInternationalComposition =
() => loaded ? info.ace_getInInternationalComposition() : null;
-
// prepareUserChangeset:
// Returns null if no new changes or ACE not ready. Otherwise, bundles up all user changes
// to the latest base text into a Changeset, which is returned (as a string if encodeAsString).
@@ -148,7 +121,6 @@ const Ace2Editor = function () {
// prepareUserChangeset will return an updated changeset that takes into account the latest user
// changes, and modify the changeset to be applied by applyPreparedChangesetToBase accordingly.
this.prepareUserChangeset = () => loaded ? info.ace_prepareUserChangeset() : null;
-
const addStyleTagsFor = (doc, files) => {
for (const file of files) {
const link = doc.createElement('link');
@@ -158,17 +130,14 @@ const Ace2Editor = function () {
doc.head.appendChild(link);
}
};
-
this.destroy = pendingInit(() => {
info.ace_dispose();
info.frame.parentNode.removeChild(info.frame);
info = null; // prevent IE 6 closure memory leaks
});
-
this.init = async function (containerId, initialCode) {
debugLog('Ace2Editor.init()');
this.importText(initialCode);
-
const includedCSS = [
`../static/css/iframe_editor.css?v=${clientVars.randomVersionString}`,
`../static/css/pad.css?v=${clientVars.randomVersionString}`,
@@ -177,12 +146,9 @@ const Ace2Editor = function () {
(p) => /\/\//.test(p) ? p : `../static/plugins/${p}`),
`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`,
];
-
const skinVariants = clientVars.skinVariants.split(' ').filter((x) => x !== '');
-
const outerFrame = document.createElement('iframe');
outerFrame.name = 'ace_outer';
- outerFrame.frameBorder = 0; // for IE
outerFrame.title = 'Ether';
// Some browsers do strange things unless the iframe has a src or srcdoc property:
// - Firefox replaces the frame's contentWindow.document object with a different object after
@@ -197,25 +163,20 @@ const Ace2Editor = function () {
info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame);
const outerWindow = outerFrame.contentWindow;
-
debugLog('Ace2Editor.init() waiting for outer frame');
await frameReady(outerFrame);
debugLog('Ace2Editor.init() outer frame ready');
-
// Firefox might replace the outerWindow.document object after iframe creation so this variable
// is assigned after the Window's load event.
const outerDocument = outerWindow.document;
-
// tag
outerDocument.documentElement.classList.add('outer-editor', 'outerdoc', ...skinVariants);
-
// tag
addStyleTagsFor(outerDocument, includedCSS);
const outerStyle = outerDocument.createElement('style');
outerStyle.type = 'text/css';
outerStyle.title = 'dynamicsyntax';
outerDocument.head.appendChild(outerStyle);
-
// tag
outerDocument.body.id = 'outerdocbody';
outerDocument.body.classList.add('outerdocbody', ...pluginUtils.clientPluginNames());
@@ -231,30 +192,24 @@ const Ace2Editor = function () {
lineMetricsDiv.id = 'linemetricsdiv';
lineMetricsDiv.appendChild(outerDocument.createTextNode('x'));
outerDocument.body.appendChild(lineMetricsDiv);
-
const innerFrame = outerDocument.createElement('iframe');
innerFrame.name = 'ace_inner';
innerFrame.title = 'pad';
innerFrame.scrolling = 'no';
- innerFrame.frameBorder = 0;
- innerFrame.allowTransparency = true; // for IE
+ innerFrame.frameBorder = String(0);
// The iframe MUST have a src or srcdoc property to avoid browser quirks. See the comment above
// outerFrame.srcdoc.
innerFrame.src = 'empty.html';
outerDocument.body.insertBefore(innerFrame, outerDocument.body.firstChild);
- const innerWindow = innerFrame.contentWindow;
-
+ const innerWindow = innerFrame.contentWindow as unknown as CustomWindow;
debugLog('Ace2Editor.init() waiting for inner frame');
await frameReady(innerFrame);
debugLog('Ace2Editor.init() inner frame ready');
-
// Firefox might replace the innerWindow.document object after iframe creation so this variable
// is assigned after the Window's load event.
const innerDocument = innerWindow.document;
-
// tag
innerDocument.documentElement.classList.add('inner-editor', ...skinVariants);
-
// tag
addStyleTagsFor(innerDocument, includedCSS);
const requireKernel = innerDocument.createElement('script');
@@ -267,7 +222,7 @@ const Ace2Editor = function () {
const script = innerDocument.createElement('script');
script.type = 'text/javascript';
script.src = absUrl(`../javascripts/lib/ep_etherpad-lite/static/js/${module}.js` +
- `?callback=require.define&v=${clientVars.randomVersionString}`);
+ `?callback=require.define&v=${clientVars.randomVersionString}`);
innerDocument.head.appendChild(script);
}
const innerStyle = innerDocument.createElement('style');
@@ -275,16 +230,13 @@ const Ace2Editor = function () {
innerStyle.title = 'dynamicsyntax';
innerDocument.head.appendChild(innerStyle);
const headLines = [];
- hooks.callAll('aceInitInnerdocbodyHead', {iframeHTML: headLines});
- innerDocument.head.appendChild(
- innerDocument.createRange().createContextualFragment(headLines.join('\n')));
-
+ hooks.callAll('aceInitInnerdocbodyHead', { iframeHTML: headLines });
+ innerDocument.head.appendChild(innerDocument.createRange().createContextualFragment(headLines.join('\n')));
// tag
innerDocument.body.id = 'innerdocbody';
innerDocument.body.classList.add('innerdocbody');
innerDocument.body.setAttribute('spellcheck', 'false');
innerDocument.body.appendChild(innerDocument.createTextNode('\u00A0')); //
-
debugLog('Ace2Editor.init() waiting for require kernel load');
await eventFired(requireKernel, 'load');
debugLog('Ace2Editor.init() require kernel loaded');
@@ -292,22 +244,18 @@ const Ace2Editor = function () {
require.setRootURI(absUrl('../javascripts/src'));
require.setLibraryURI(absUrl('../javascripts/lib'));
require.setGlobalKeyPath('require');
-
// intentially moved before requiring client_plugins to save a 307
- innerWindow.Ace2Inner = require('ep_etherpad-lite/static/js/ace2_inner');
- innerWindow.plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins');
+ innerWindow.Ace2Inner = required('ep_etherpad-lite/static/js/ace2_inner');
+ innerWindow.plugins = required('ep_etherpad-lite/static/js/pluginfw/client_plugins');
innerWindow.plugins.adoptPluginsFromAncestorsOf(innerWindow);
-
- innerWindow.$ = innerWindow.jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery;
-
+ innerWindow.$ = innerWindow.jQuery = required('ep_etherpad-lite/static/js/rjquery').jQuery;
debugLog('Ace2Editor.init() waiting for plugins');
- await new Promise((resolve, reject) => innerWindow.plugins.ensure(
- (err) => err != null ? reject(err) : resolve()));
+ await new Promise((resolve, reject) => innerWindow.plugins.ensure((err) => err != null ? reject(err) : resolve()));
debugLog('Ace2Editor.init() waiting for Ace2Inner.init()');
await innerWindow.Ace2Inner.init(info, {
inner: makeCSSManager(innerStyle.sheet),
outer: makeCSSManager(outerStyle.sheet),
- parent: makeCSSManager(document.querySelector('style[title="dynamicsyntax"]').sheet),
+ parent: makeCSSManager((document.querySelector('style[title="dynamicsyntax"]') as unknown as CustomElementWithSheet).sheet),
});
debugLog('Ace2Editor.init() Ace2Inner.init() returned');
loaded = true;
@@ -315,5 +263,4 @@ const Ace2Editor = function () {
debugLog('Ace2Editor.init() done');
};
};
-
-exports.Ace2Editor = Ace2Editor;
+export { Ace2Editor };
diff --git a/src/static/js/ace2_common.js b/src/static/js/ace2_common.ts
similarity index 78%
rename from src/static/js/ace2_common.js
rename to src/static/js/ace2_common.ts
index c1dab5cfd8b..683dace3ad6 100644
--- a/src/static/js/ace2_common.js
+++ b/src/static/js/ace2_common.ts
@@ -22,11 +22,11 @@
* limitations under the License.
*/
-const isNodeText = (node) => (node.nodeType === 3);
+export const isNodeText = (node) => (node.nodeType === 3);
-const getAssoc = (obj, name) => obj[`_magicdom_${name}`];
+export const getAssoc = (obj, name) => obj[`_magicdom_${name}`];
-const setAssoc = (obj, name, value) => {
+export const setAssoc = (obj, name, value) => {
// note that in IE designMode, properties of a node can get
// copied to new nodes that are spawned during editing; also,
// properties representable in HTML text can survive copy-and-paste
@@ -38,7 +38,7 @@ const setAssoc = (obj, name, value) => {
// between false and true, a number between 0 and numItems inclusive.
-const binarySearch = (numItems, func) => {
+export const binarySearch = (numItems, func) => {
if (numItems < 1) return 0;
if (func(0)) return 0;
if (!func(numItems - 1)) return numItems;
@@ -52,17 +52,10 @@ const binarySearch = (numItems, func) => {
return high;
};
-const binarySearchInfinite = (expectedLength, func) => {
+export const binarySearchInfinite = (expectedLength, func) => {
let i = 0;
while (!func(i)) i += expectedLength;
return binarySearch(i, func);
};
-const noop = () => {};
-
-exports.isNodeText = isNodeText;
-exports.getAssoc = getAssoc;
-exports.setAssoc = setAssoc;
-exports.binarySearch = binarySearch;
-exports.binarySearchInfinite = binarySearchInfinite;
-exports.noop = noop;
+export const noop = () => {};
diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js
deleted file mode 100644
index a8b98ac16db..00000000000
--- a/src/static/js/ace2_inner.js
+++ /dev/null
@@ -1,3527 +0,0 @@
-'use strict';
-
-/**
- * Copyright 2009 Google Inc.
- * Copyright 2020 John McLear - The Etherpad Foundation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-let documentAttributeManager;
-
-const AttributeMap = require('./AttributeMap');
-const browser = require('./vendors/browser');
-const padutils = require('./pad_utils').padutils;
-const Ace2Common = require('./ace2_common');
-const $ = require('./rjquery').$;
-
-const isNodeText = Ace2Common.isNodeText;
-const getAssoc = Ace2Common.getAssoc;
-const setAssoc = Ace2Common.setAssoc;
-const noop = Ace2Common.noop;
-const hooks = require('./pluginfw/hooks');
-
-function Ace2Inner(editorInfo, cssManagers) {
- const makeChangesetTracker = require('./changesettracker').makeChangesetTracker;
- const colorutils = require('./colorutils').colorutils;
- const makeContentCollector = require('./contentcollector').makeContentCollector;
- const domline = require('./domline').domline;
- const AttribPool = require('./AttributePool');
- const Changeset = require('./Changeset');
- const ChangesetUtils = require('./ChangesetUtils');
- const linestylefilter = require('./linestylefilter').linestylefilter;
- const SkipList = require('./skiplist');
- const undoModule = require('./undomodule').undoModule;
- const AttributeManager = require('./AttributeManager');
- const Scroll = require('./scroll');
- const DEBUG = false;
-
- const THE_TAB = ' '; // 4
- const MAX_LIST_LEVEL = 16;
-
- const FORMATTING_STYLES = ['bold', 'italic', 'underline', 'strikethrough'];
- const SELECT_BUTTON_CLASS = 'selected';
-
- let thisAuthor = '';
-
- let disposed = false;
-
- const focus = () => {
- window.focus();
- };
-
- const outerWin = window.parent;
- const outerDoc = outerWin.document;
- const sideDiv = outerDoc.getElementById('sidediv');
- const lineMetricsDiv = outerDoc.getElementById('linemetricsdiv');
- const sideDivInner = outerDoc.getElementById('sidedivinner');
- const appendNewSideDivLine = () => {
- const lineDiv = outerDoc.createElement('div');
- sideDivInner.appendChild(lineDiv);
- const lineSpan = outerDoc.createElement('span');
- lineSpan.classList.add('line-number');
- lineSpan.appendChild(outerDoc.createTextNode(sideDivInner.children.length));
- lineDiv.appendChild(lineSpan);
- };
- appendNewSideDivLine();
-
- const scroll = Scroll.init(outerWin);
-
- let outsideKeyDown = noop;
- let outsideKeyPress = (e) => true;
- let outsideNotifyDirty = noop;
-
- /**
- * Document representation.
- */
- const rep = {
- /**
- * The contents of the document. Each entry in this skip list is an object representing a
- * line (actually paragraph) of text. The line objects are created by createDomLineEntry().
- */
- lines: new SkipList(),
- /**
- * Start of the selection. Represented as an array of two non-negative numbers that point to the
- * first character of the selection: [zeroBasedLineNumber, zeroBasedColumnNumber]. Notes:
- * - There is an implicit newline character (not actually stored) at the end of every line.
- * Because of this, a selection that starts at the end of a line (column number equals the
- * number of characters in the line, not including the implicit newline) is not equivalent
- * to a selection that starts at the beginning of the next line. The same goes for the
- * selection end.
- * - If there are N lines, [N, 0] is valid for the start of the selection. [N, 0] indicates
- * that the selection starts just after the implicit newline at the end of the document's
- * last line (if the document has any lines). The same goes for the end of the selection.
- * - If a line starts with a line marker, a selection that starts at the beginning of the line
- * may start either immediately before (column = 0) or immediately after (column = 1) the
- * line marker, and the two are considered to be semantically equivalent. For safety, all
- * code should be written to accept either but only produce selections that start after the
- * line marker (the column number should be 1, not 0, when there is a line marker). The same
- * goes for the end of the selection.
- */
- selStart: null,
- /**
- * End of the selection. Represented as an array of two non-negative numbers that point to the
- * character just after the end of the selection: [zeroBasedLineNumber, zeroBasedColumnNumber].
- * See the above notes for selStart.
- */
- selEnd: null,
- /**
- * Whether the selection extends "backwards", so that the focus point (controlled with the arrow
- * keys) is at the beginning. This is not supported in IE, though native IE selections have that
- * behavior (which we try not to interfere with). Must be false if selection is collapsed!
- */
- selFocusAtStart: false,
- alltext: '',
- alines: [],
- apool: new AttribPool(),
- };
-
- // lines, alltext, alines, and DOM are set up in init()
- if (undoModule.enabled) {
- undoModule.apool = rep.apool;
- }
-
- let isEditable = true;
- let doesWrap = true;
- let hasLineNumbers = true;
- let isStyled = true;
-
- let console = (DEBUG && window.console);
-
- if (!window.console) {
- const names = [
- 'log',
- 'debug',
- 'info',
- 'warn',
- 'error',
- 'assert',
- 'dir',
- 'dirxml',
- 'group',
- 'groupEnd',
- 'time',
- 'timeEnd',
- 'count',
- 'trace',
- 'profile',
- 'profileEnd',
- ];
- console = {};
- for (const name of names) console[name] = noop;
- }
-
- const scheduler = parent; // hack for opera required
-
- const performDocumentReplaceRange = (start, end, newText) => {
- if (start === undefined) start = rep.selStart;
- if (end === undefined) end = rep.selEnd;
-
- // start[0]: <--- start[1] --->CCCCCCCCCCC\n
- // CCCCCCCCCCCCCCCCCCCC\n
- // CCCC\n
- // end[0]: -------\n
- const builder = Changeset.builder(rep.lines.totalWidth());
- ChangesetUtils.buildKeepToStartOfRange(rep, builder, start);
- ChangesetUtils.buildRemoveRange(rep, builder, start, end);
- builder.insert(newText, [
- ['author', thisAuthor],
- ], rep.apool);
- const cs = builder.toString();
-
- performDocumentApplyChangeset(cs);
- };
-
- const changesetTracker = makeChangesetTracker(scheduler, rep.apool, {
- withCallbacks: (operationName, f) => {
- inCallStackIfNecessary(operationName, () => {
- fastIncorp(1);
- f(
- {
- setDocumentAttributedText: (atext) => {
- setDocAText(atext);
- },
- applyChangesetToDocument: (changeset, preferInsertionAfterCaret) => {
- const oldEventType = currentCallStack.editEvent.eventType;
- currentCallStack.startNewEvent('nonundoable');
-
- performDocumentApplyChangeset(changeset, preferInsertionAfterCaret);
-
- currentCallStack.startNewEvent(oldEventType);
- },
- });
- });
- },
- });
-
- const authorInfos = {}; // presence of key determines if author is present in doc
- const getAuthorInfos = () => authorInfos;
- editorInfo.ace_getAuthorInfos = getAuthorInfos;
-
- const setAuthorStyle = (author, info) => {
- const authorSelector = getAuthorColorClassSelector(getAuthorClassName(author));
-
- const authorStyleSet = hooks.callAll('aceSetAuthorStyle', {
- dynamicCSS: cssManagers.inner,
- outerDynamicCSS: cssManagers.outer,
- parentDynamicCSS: cssManagers.parent,
- info,
- author,
- authorSelector,
- });
-
- // Prevent default behaviour if any hook says so
- if (authorStyleSet.some((it) => it)) {
- return;
- }
-
- if (!info) {
- cssManagers.inner.removeSelectorStyle(authorSelector);
- cssManagers.parent.removeSelectorStyle(authorSelector);
- } else if (info.bgcolor) {
- let bgcolor = info.bgcolor;
- if ((typeof info.fade) === 'number') {
- bgcolor = fadeColor(bgcolor, info.fade);
- }
- const textColor =
- colorutils.textColorFromBackgroundColor(bgcolor, parent.parent.clientVars.skinName);
- const styles = [
- cssManagers.inner.selectorStyle(authorSelector),
- cssManagers.parent.selectorStyle(authorSelector),
- ];
- for (const style of styles) {
- style.backgroundColor = bgcolor;
- style.color = textColor;
- style['padding-top'] = '3px';
- style['padding-bottom'] = '4px';
- }
- }
- };
-
- const setAuthorInfo = (author, info) => {
- if (!author) return; // author ID not set for some reason
- if ((typeof author) !== 'string') {
- // Potentially caused by: https://github.com/ether/etherpad-lite/issues/2802");
- throw new Error(`setAuthorInfo: author (${author}) is not a string`);
- }
- if (!info) {
- delete authorInfos[author];
- } else {
- authorInfos[author] = info;
- }
- setAuthorStyle(author, info);
- };
-
- const getAuthorClassName = (author) => `author-${author.replace(/[^a-y0-9]/g, (c) => {
- if (c === '.') return '-';
- return `z${c.charCodeAt(0)}z`;
- })}`;
-
- const className2Author = (className) => {
- if (className.substring(0, 7) === 'author-') {
- return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, (cc) => {
- if (cc === '-') { return '.'; } else if (cc.charAt(0) === 'z') {
- return String.fromCharCode(Number(cc.slice(1, -1)));
- } else {
- return cc;
- }
- });
- }
- return null;
- };
-
- const getAuthorColorClassSelector = (oneClassName) => `.authorColors .${oneClassName}`;
-
- const fadeColor = (colorCSS, fadeFrac) => {
- let color = colorutils.css2triple(colorCSS);
- color = colorutils.blend(color, [1, 1, 1], fadeFrac);
- return colorutils.triple2css(color);
- };
-
- editorInfo.ace_getRep = () => rep;
-
- editorInfo.ace_getAuthor = () => thisAuthor;
-
- const _nonScrollableEditEvents = {
- applyChangesToBase: 1,
- };
-
- for (const eventType of hooks.callAll('aceRegisterNonScrollableEditEvents')) {
- _nonScrollableEditEvents[eventType] = 1;
- }
-
- const isScrollableEditEvent = (eventType) => !_nonScrollableEditEvents[eventType];
-
- let currentCallStack = null;
-
- const inCallStack = (type, action) => {
- if (disposed) return;
-
- const newEditEvent = (eventType) => ({
- eventType,
- backset: null,
- });
-
- const submitOldEvent = (evt) => {
- if (rep.selStart && rep.selEnd) {
- const selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1];
- const selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1];
- evt.selStart = selStartChar;
- evt.selEnd = selEndChar;
- evt.selFocusAtStart = rep.selFocusAtStart;
- }
- if (undoModule.enabled) {
- let undoWorked = false;
- try {
- if (isPadLoading(evt.eventType)) {
- undoModule.clearHistory();
- } else if (evt.eventType === 'nonundoable') {
- if (evt.changeset) {
- undoModule.reportExternalChange(evt.changeset);
- }
- } else {
- undoModule.reportEvent(evt);
- }
- undoWorked = true;
- } finally {
- if (!undoWorked) {
- undoModule.enabled = false; // for safety
- }
- }
- }
- };
-
- const startNewEvent = (eventType, dontSubmitOld) => {
- const oldEvent = currentCallStack.editEvent;
- if (!dontSubmitOld) {
- submitOldEvent(oldEvent);
- }
- currentCallStack.editEvent = newEditEvent(eventType);
- return oldEvent;
- };
-
- currentCallStack = {
- type,
- docTextChanged: false,
- selectionAffected: false,
- userChangedSelection: false,
- domClean: false,
- isUserChange: false,
- // is this a "user change" type of call-stack
- repChanged: false,
- editEvent: newEditEvent(type),
- startNewEvent,
- };
- let cleanExit = false;
- let result;
- try {
- result = action();
-
- hooks.callAll('aceEditEvent', {
- callstack: currentCallStack,
- editorInfo,
- rep,
- documentAttributeManager,
- });
-
- cleanExit = true;
- } finally {
- const cs = currentCallStack;
- if (cleanExit) {
- submitOldEvent(cs.editEvent);
- if (cs.domClean && cs.type !== 'setup') {
- if (cs.selectionAffected) {
- updateBrowserSelectionFromRep();
- }
- if ((cs.docTextChanged || cs.userChangedSelection) && isScrollableEditEvent(cs.type)) {
- scrollSelectionIntoView();
- }
- if (cs.docTextChanged && cs.type.indexOf('importText') < 0) {
- outsideNotifyDirty();
- }
- }
- } else if (currentCallStack.type === 'idleWorkTimer') {
- idleWorkTimer.atLeast(1000);
- }
- currentCallStack = null;
- }
- return result;
- };
- editorInfo.ace_inCallStack = inCallStack;
-
- const inCallStackIfNecessary = (type, action) => {
- if (!currentCallStack) {
- inCallStack(type, action);
- } else {
- action();
- }
- };
- editorInfo.ace_inCallStackIfNecessary = inCallStackIfNecessary;
-
- const dispose = () => {
- disposed = true;
- if (idleWorkTimer) idleWorkTimer.never();
- teardown();
- };
-
- const setWraps = (newVal) => {
- doesWrap = newVal;
- document.body.classList.toggle('doesWrap', doesWrap);
- scheduler.setTimeout(() => {
- inCallStackIfNecessary('setWraps', () => {
- fastIncorp(7);
- recreateDOM();
- fixView();
- });
- }, 0);
- };
-
- const setStyled = (newVal) => {
- const oldVal = isStyled;
- isStyled = !!newVal;
-
- if (newVal !== oldVal) {
- if (!newVal) {
- // clear styles
- inCallStackIfNecessary('setStyled', () => {
- fastIncorp(12);
- const clearStyles = [];
- for (const k of Object.keys(STYLE_ATTRIBS)) {
- clearStyles.push([k, '']);
- }
- performDocumentApplyAttributesToCharRange(0, rep.alltext.length, clearStyles);
- });
- }
- }
- };
-
- const setTextFace = (face) => {
- document.body.style.fontFamily = face;
- lineMetricsDiv.style.fontFamily = face;
- };
-
- const recreateDOM = () => {
- // precond: normalized
- recolorLinesInRange(0, rep.alltext.length);
- };
-
- const setEditable = (newVal) => {
- isEditable = newVal;
- document.body.contentEditable = isEditable ? 'true' : 'false';
- document.body.classList.toggle('static', !isEditable);
- };
-
- const enforceEditability = () => setEditable(isEditable);
-
- const importText = (text, undoable, dontProcess) => {
- let lines;
- if (dontProcess) {
- if (text.charAt(text.length - 1) !== '\n') {
- throw new Error('new raw text must end with newline');
- }
- if (/[\r\t\xa0]/.exec(text)) {
- throw new Error('new raw text must not contain CR, tab, or nbsp');
- }
- lines = text.substring(0, text.length - 1).split('\n');
- } else {
- lines = text.split('\n').map(textify);
- }
- let newText = '\n';
- if (lines.length > 0) {
- newText = `${lines.join('\n')}\n`;
- }
-
- inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => {
- setDocText(newText);
- });
-
- if (dontProcess && rep.alltext !== text) {
- throw new Error('mismatch error setting raw text in importText');
- }
- };
-
- const importAText = (atext, apoolJsonObj, undoable) => {
- atext = Changeset.cloneAText(atext);
- if (apoolJsonObj) {
- const wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
- atext.attribs = Changeset.moveOpsToNewPool(atext.attribs, wireApool, rep.apool);
- }
- inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => {
- setDocAText(atext);
- });
- };
-
- const setDocAText = (atext) => {
- if (atext.text === '') {
- /*
- * The server is fine with atext.text being an empty string, but the front
- * end is not, and crashes.
- *
- * It is not clear if this is a problem in the server or in the client
- * code, and this is a client-side hack fix. The underlying problem needs
- * to be investigated.
- *
- * See for reference:
- * - https://github.com/ether/etherpad-lite/issues/3861
- */
- atext.text = '\n';
- }
-
- fastIncorp(8);
-
- const oldLen = rep.lines.totalWidth();
- const numLines = rep.lines.length();
- const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
- const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
- const assem = Changeset.smartOpAssembler();
- const o = new Changeset.Op('-');
- o.chars = upToLastLine;
- o.lines = numLines - 1;
- assem.append(o);
- o.chars = lastLineLength;
- o.lines = 0;
- assem.append(o);
- for (const op of Changeset.opsFromAText(atext)) assem.append(op);
- const newLen = oldLen + assem.getLengthChange();
- const changeset = Changeset.checkRep(
- Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
- performDocumentApplyChangeset(changeset);
-
- performSelectionChange(
- [0, rep.lines.atIndex(0).lineMarker], [0, rep.lines.atIndex(0).lineMarker]);
-
- idleWorkTimer.atMost(100);
-
- if (rep.alltext !== atext.text) {
- throw new Error('mismatch error setting raw text in setDocAText');
- }
- };
-
- const setDocText = (text) => {
- setDocAText(Changeset.makeAText(text));
- };
-
- const getDocText = () => {
- const alltext = rep.alltext;
- let len = alltext.length;
- if (len > 0) len--; // final extra newline
- return alltext.substring(0, len);
- };
-
- const exportText = () => {
- if (currentCallStack && !currentCallStack.domClean) {
- inCallStackIfNecessary('exportText', () => {
- fastIncorp(2);
- });
- }
- return getDocText();
- };
-
- const editorChangedSize = () => fixView();
-
- const setOnKeyPress = (handler) => {
- outsideKeyPress = handler;
- };
-
- const setOnKeyDown = (handler) => {
- outsideKeyDown = handler;
- };
-
- const setNotifyDirty = (handler) => {
- outsideNotifyDirty = handler;
- };
-
- const CMDS = {
- clearauthorship: (prompt) => {
- if ((!(rep.selStart && rep.selEnd)) || isCaret()) {
- if (prompt) {
- prompt();
- } else {
- performDocumentApplyAttributesToCharRange(0, rep.alltext.length, [
- ['author', ''],
- ]);
- }
- } else {
- setAttributeOnSelection('author', '');
- }
- },
- };
-
- const execCommand = (cmd, ...args) => {
- cmd = cmd.toLowerCase();
- if (CMDS[cmd]) {
- inCallStackIfNecessary(cmd, () => {
- fastIncorp(9);
- CMDS[cmd](...args);
- });
- }
- };
-
- const replaceRange = (start, end, text) => {
- inCallStackIfNecessary('replaceRange', () => {
- fastIncorp(9);
- performDocumentReplaceRange(start, end, text);
- });
- };
-
- editorInfo.ace_callWithAce = (fn, callStack, normalize) => {
- let wrapper = () => fn(editorInfo);
-
- if (normalize !== undefined) {
- const wrapper1 = wrapper;
- wrapper = () => {
- editorInfo.ace_fastIncorp(9);
- wrapper1();
- };
- }
-
- if (callStack !== undefined) {
- return editorInfo.ace_inCallStack(callStack, wrapper);
- } else {
- return wrapper();
- }
- };
-
- /**
- * This methed exposes a setter for some ace properties
- * @param key the name of the parameter
- * @param value the value to set to
- */
- editorInfo.ace_setProperty = (key, value) => {
- // These properties are exposed
- const setters = {
- wraps: setWraps,
- showsauthorcolors: (val) => document.body.classList.toggle('authorColors', !!val),
- showsuserselections: (val) => document.body.classList.toggle('userSelections', !!val),
- showslinenumbers: (value) => {
- hasLineNumbers = !!value;
- sideDiv.parentNode.classList.toggle('line-numbers-hidden', !hasLineNumbers);
- fixView();
- },
- userauthor: (value) => {
- thisAuthor = String(value);
- documentAttributeManager.author = thisAuthor;
- },
- styled: setStyled,
- textface: setTextFace,
- rtlistrue: (value) => {
- document.body.classList.toggle('rtl', value);
- document.body.classList.toggle('ltr', !value);
- document.documentElement.dir = value ? 'rtl' : 'ltr';
- },
- };
-
- const setter = setters[key.toLowerCase()];
-
- // check if setter is present
- if (setter !== undefined) {
- setter(value);
- }
- };
-
- editorInfo.ace_setBaseText = (txt) => {
- changesetTracker.setBaseText(txt);
- };
- editorInfo.ace_setBaseAttributedText = (atxt, apoolJsonObj) => {
- changesetTracker.setBaseAttributedText(atxt, apoolJsonObj);
- };
- editorInfo.ace_applyChangesToBase = (c, optAuthor, apoolJsonObj) => {
- changesetTracker.applyChangesToBase(c, optAuthor, apoolJsonObj);
- };
- editorInfo.ace_prepareUserChangeset = () => changesetTracker.prepareUserChangeset();
- editorInfo.ace_applyPreparedChangesetToBase = () => {
- changesetTracker.applyPreparedChangesetToBase();
- };
- editorInfo.ace_setUserChangeNotificationCallback = (f) => {
- changesetTracker.setUserChangeNotificationCallback(f);
- };
- editorInfo.ace_setAuthorInfo = (author, info) => {
- setAuthorInfo(author, info);
- };
-
- editorInfo.ace_getDocument = () => document;
-
- const now = () => Date.now();
-
- const newTimeLimit = (ms) => {
- const startTime = now();
- let exceededAlready = false;
- let printedTrace = false;
- const isTimeUp = () => {
- if (exceededAlready) {
- if ((!printedTrace)) {
- printedTrace = true;
- }
- return true;
- }
- const elapsed = now() - startTime;
- if (elapsed > ms) {
- exceededAlready = true;
- return true;
- } else {
- return false;
- }
- };
-
- isTimeUp.elapsed = () => now() - startTime;
- return isTimeUp;
- };
-
-
- const makeIdleAction = (func) => {
- let scheduledTimeout = null;
- let scheduledTime = 0;
-
- const unschedule = () => {
- if (scheduledTimeout) {
- scheduler.clearTimeout(scheduledTimeout);
- scheduledTimeout = null;
- }
- };
-
- const reschedule = (time) => {
- unschedule();
- scheduledTime = time;
- let delay = time - now();
- if (delay < 0) delay = 0;
- scheduledTimeout = scheduler.setTimeout(callback, delay);
- };
-
- const callback = () => {
- scheduledTimeout = null;
- // func may reschedule the action
- func();
- };
-
- return {
- atMost: (ms) => {
- const latestTime = now() + ms;
- if ((!scheduledTimeout) || scheduledTime > latestTime) {
- reschedule(latestTime);
- }
- },
- // atLeast(ms) will schedule the action if not scheduled yet.
- // In other words, "infinity" is replaced by ms, even though
- // it is technically larger.
- atLeast: (ms) => {
- const earliestTime = now() + ms;
- if ((!scheduledTimeout) || scheduledTime < earliestTime) {
- reschedule(earliestTime);
- }
- },
- never: () => {
- unschedule();
- },
- };
- };
-
- const fastIncorp = (n) => {
- // normalize but don't do any lexing or anything
- incorporateUserChanges();
- };
- editorInfo.ace_fastIncorp = fastIncorp;
-
- const idleWorkTimer = makeIdleAction(() => {
- if (inInternationalComposition) {
- // don't do idle input incorporation during international input composition
- idleWorkTimer.atLeast(500);
- return;
- }
-
- inCallStackIfNecessary('idleWorkTimer', () => {
- const isTimeUp = newTimeLimit(250);
-
- let finishedImportantWork = false;
- let finishedWork = false;
-
- try {
- incorporateUserChanges();
-
- if (isTimeUp()) return;
-
- updateLineNumbers(); // update line numbers if any time left
- if (isTimeUp()) return;
- finishedImportantWork = true;
- finishedWork = true;
- } finally {
- if (finishedWork) {
- idleWorkTimer.atMost(1000);
- } else if (finishedImportantWork) {
- // if we've finished highlighting the view area,
- // more highlighting could be counter-productive,
- // e.g. if the user just opened a triple-quote and will soon close it.
- idleWorkTimer.atMost(500);
- } else {
- let timeToWait = Math.round(isTimeUp.elapsed() / 2);
- if (timeToWait < 100) timeToWait = 100;
- idleWorkTimer.atMost(timeToWait);
- }
- }
- });
- });
-
- let _nextId = 1;
-
- const uniqueId = (n) => {
- // not actually guaranteed to be unique, e.g. if user copy-pastes
- // nodes with ids
- const nid = n.id;
- if (nid) return nid;
- return (n.id = `magicdomid${_nextId++}`);
- };
-
-
- const recolorLinesInRange = (startChar, endChar) => {
- if (endChar <= startChar) return;
- if (startChar < 0 || startChar >= rep.lines.totalWidth()) return;
- let lineEntry = rep.lines.atOffset(startChar); // rounds down to line boundary
- let lineStart = rep.lines.offsetOfEntry(lineEntry);
- let lineIndex = rep.lines.indexOfEntry(lineEntry);
- let selectionNeedsResetting = false;
- let firstLine = null;
-
- // tokenFunc function; accesses current value of lineEntry and curDocChar,
- // also mutates curDocChar
- const tokenFunc = (tokenText, tokenClass) => {
- lineEntry.domInfo.appendSpan(tokenText, tokenClass);
- };
-
- while (lineEntry && lineStart < endChar) {
- const lineEnd = lineStart + lineEntry.width;
- lineEntry.domInfo.clearSpans();
- getSpansForLine(lineEntry, tokenFunc, lineStart);
- lineEntry.domInfo.finishUpdate();
-
- markNodeClean(lineEntry.lineNode);
-
- if (rep.selStart && rep.selStart[0] === lineIndex ||
- rep.selEnd && rep.selEnd[0] === lineIndex) {
- selectionNeedsResetting = true;
- }
-
- if (firstLine == null) firstLine = lineIndex;
- lineStart = lineEnd;
- lineEntry = rep.lines.next(lineEntry);
- lineIndex++;
- }
- if (selectionNeedsResetting) {
- currentCallStack.selectionAffected = true;
- }
- };
-
- // like getSpansForRange, but for a line, and the func takes (text,class)
- // instead of (width,class); excludes the trailing '\n' from
- // consideration by func
-
-
- const getSpansForLine = (lineEntry, textAndClassFunc, lineEntryOffsetHint) => {
- let lineEntryOffset = lineEntryOffsetHint;
- if ((typeof lineEntryOffset) !== 'number') {
- lineEntryOffset = rep.lines.offsetOfEntry(lineEntry);
- }
- const text = lineEntry.text;
- if (text.length === 0) {
- // allow getLineStyleFilter to set line-div styles
- const func = linestylefilter.getLineStyleFilter(
- 0, '', textAndClassFunc, rep.apool);
- func('', '');
- } else {
- let filteredFunc = linestylefilter.getFilterStack(text, textAndClassFunc, browser);
- const lineNum = rep.lines.indexOfEntry(lineEntry);
- const aline = rep.alines[lineNum];
- filteredFunc = linestylefilter.getLineStyleFilter(
- text.length, aline, filteredFunc, rep.apool);
- filteredFunc(text, '');
- }
- };
-
- let observedChanges;
-
- const clearObservedChanges = () => {
- observedChanges = {
- cleanNodesNearChanges: {},
- };
- };
- clearObservedChanges();
-
- const getCleanNodeByKey = (key) => {
- let n = document.getElementById(key);
- // copying and pasting can lead to duplicate ids
- while (n && isNodeDirty(n)) {
- n.id = '';
- n = document.getElementById(key);
- }
- return n;
- };
-
- const observeChangesAroundNode = (node) => {
- // Around this top-level DOM node, look for changes to the document
- // (from how it looks in our representation) and record them in a way
- // that can be used to "normalize" the document (apply the changes to our
- // representation, and put the DOM in a canonical form).
- let cleanNode;
- let hasAdjacentDirtyness;
- if (!isNodeDirty(node)) {
- cleanNode = node;
- const prevSib = cleanNode.previousSibling;
- const nextSib = cleanNode.nextSibling;
- hasAdjacentDirtyness = ((prevSib && isNodeDirty(prevSib)) ||
- (nextSib && isNodeDirty(nextSib)));
- } else {
- // node is dirty, look for clean node above
- let upNode = node.previousSibling;
- while (upNode && isNodeDirty(upNode)) {
- upNode = upNode.previousSibling;
- }
- if (upNode) {
- cleanNode = upNode;
- } else {
- let downNode = node.nextSibling;
- while (downNode && isNodeDirty(downNode)) {
- downNode = downNode.nextSibling;
- }
- if (downNode) {
- cleanNode = downNode;
- }
- }
- if (!cleanNode) {
- // Couldn't find any adjacent clean nodes!
- // Since top and bottom of doc is dirty, the dirty area will be detected.
- return;
- }
- hasAdjacentDirtyness = true;
- }
-
- if (hasAdjacentDirtyness) {
- // previous or next line is dirty
- observedChanges.cleanNodesNearChanges[`$${uniqueId(cleanNode)}`] = true;
- } else {
- // next and prev lines are clean (if they exist)
- const lineKey = uniqueId(cleanNode);
- const prevSib = cleanNode.previousSibling;
- const nextSib = cleanNode.nextSibling;
- const actualPrevKey = ((prevSib && uniqueId(prevSib)) || null);
- const actualNextKey = ((nextSib && uniqueId(nextSib)) || null);
- const repPrevEntry = rep.lines.prev(rep.lines.atKey(lineKey));
- const repNextEntry = rep.lines.next(rep.lines.atKey(lineKey));
- const repPrevKey = ((repPrevEntry && repPrevEntry.key) || null);
- const repNextKey = ((repNextEntry && repNextEntry.key) || null);
- if (actualPrevKey !== repPrevKey || actualNextKey !== repNextKey) {
- observedChanges.cleanNodesNearChanges[`$${uniqueId(cleanNode)}`] = true;
- }
- }
- };
-
- const observeChangesAroundSelection = () => {
- if (currentCallStack.observedSelection) return;
- currentCallStack.observedSelection = true;
-
- const selection = getSelection();
-
- if (selection) {
- const node1 = topLevel(selection.startPoint.node);
- const node2 = topLevel(selection.endPoint.node);
- if (node1) observeChangesAroundNode(node1);
- if (node2 && node1 !== node2) {
- observeChangesAroundNode(node2);
- }
- }
- };
-
- const observeSuspiciousNodes = () => {
- // inspired by Firefox bug #473255, where pasting formatted text
- // causes the cursor to jump away, making the new HTML never found.
- if (document.body.getElementsByTagName) {
- const elts = document.body.getElementsByTagName('style');
- for (const elt of elts) {
- const n = topLevel(elt);
- if (n && n.parentNode === document.body) {
- observeChangesAroundNode(n);
- }
- }
- }
- };
-
- const incorporateUserChanges = () => {
- if (currentCallStack.domClean) return false;
-
- currentCallStack.isUserChange = true;
-
- if (DEBUG && window.DONT_INCORP || window.DEBUG_DONT_INCORP) return false;
-
- // returns true if dom changes were made
- if (!document.body.firstChild) {
- document.body.innerHTML = '';
- }
-
- observeChangesAroundSelection();
- observeSuspiciousNodes();
- let dirtyRanges = getDirtyRanges();
- let dirtyRangesCheckOut = true;
- let j = 0;
- let a, b;
- let scrollToTheLeftNeeded = false;
-
- while (j < dirtyRanges.length) {
- a = dirtyRanges[j][0];
- b = dirtyRanges[j][1];
- if (!((a === 0 || getCleanNodeByKey(rep.lines.atIndex(a - 1).key)) &&
- (b === rep.lines.length() || getCleanNodeByKey(rep.lines.atIndex(b).key)))) {
- dirtyRangesCheckOut = false;
- break;
- }
- j++;
- }
- if (!dirtyRangesCheckOut) {
- for (const bodyNode of document.body.childNodes) {
- if ((bodyNode.tagName) && ((!bodyNode.id) || (!rep.lines.containsKey(bodyNode.id)))) {
- observeChangesAroundNode(bodyNode);
- }
- }
- dirtyRanges = getDirtyRanges();
- }
-
- clearObservedChanges();
-
- const selection = getSelection();
-
- let selStart, selEnd; // each one, if truthy, has [line,char] needed to set selection
- let i = 0;
- const splicesToDo = [];
- let netNumLinesChangeSoFar = 0;
- const toDeleteAtEnd = [];
- const domInsertsNeeded = []; // each entry is [nodeToInsertAfter, [info1, info2, ...]]
- while (i < dirtyRanges.length) {
- const range = dirtyRanges[i];
- a = range[0];
- b = range[1];
- let firstDirtyNode = (((a === 0) && document.body.firstChild) ||
- getCleanNodeByKey(rep.lines.atIndex(a - 1).key).nextSibling);
- firstDirtyNode = (firstDirtyNode && isNodeDirty(firstDirtyNode) && firstDirtyNode);
-
- let lastDirtyNode = (((b === rep.lines.length()) && document.body.lastChild) ||
- getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling);
-
- lastDirtyNode = (lastDirtyNode && isNodeDirty(lastDirtyNode) && lastDirtyNode);
- if (firstDirtyNode && lastDirtyNode) {
- const cc = makeContentCollector(isStyled, browser, rep.apool, className2Author);
- cc.notifySelection(selection);
- const dirtyNodes = [];
- for (let n = firstDirtyNode; n &&
- !(n.previousSibling && n.previousSibling === lastDirtyNode);
- n = n.nextSibling) {
- cc.collectContent(n);
- dirtyNodes.push(n);
- }
- cc.notifyNextNode(lastDirtyNode.nextSibling);
- let lines = cc.getLines();
- if ((lines.length <= 1 || lines[lines.length - 1] !== '') && lastDirtyNode.nextSibling) {
- // dirty region doesn't currently end a line, even taking the following node
- // (or lack of node) into account, so include the following clean node.
- // It could be SPAN or a DIV; basically this is any case where the contentCollector
- // decides it isn't done.
- // Note that this clean node might need to be there for the next dirty range.
- b++;
- const cleanLine = lastDirtyNode.nextSibling;
- cc.collectContent(cleanLine);
- toDeleteAtEnd.push(cleanLine);
- cc.notifyNextNode(cleanLine.nextSibling);
- }
-
- const ccData = cc.finish();
- const ss = ccData.selStart;
- const se = ccData.selEnd;
- lines = ccData.lines;
- const lineAttribs = ccData.lineAttribs;
- const linesWrapped = ccData.linesWrapped;
-
- if (linesWrapped > 0) {
- // Chrome decides in its infinite wisdom that it's okay to put the browser's visisble
- // window in the middle of the span. An outcome of this is that the first chars of the
- // string are no longer visible to the user.. Yay chrome.. Move the browser's visible area
- // to the left hand side of the span. Firefox isn't quite so bad, but it's still pretty
- // quirky.
- scrollToTheLeftNeeded = true;
- }
-
- if (ss[0] >= 0) selStart = [ss[0] + a + netNumLinesChangeSoFar, ss[1]];
- if (se[0] >= 0) selEnd = [se[0] + a + netNumLinesChangeSoFar, se[1]];
-
- const entries = [];
- const nodeToAddAfter = lastDirtyNode;
- const lineNodeInfos = [];
- for (const lineString of lines) {
- const newEntry = createDomLineEntry(lineString);
- entries.push(newEntry);
- lineNodeInfos.push(newEntry.domInfo);
- }
- domInsertsNeeded.push([nodeToAddAfter, lineNodeInfos]);
- for (const n of dirtyNodes) toDeleteAtEnd.push(n);
- const spliceHints = {};
- if (selStart) spliceHints.selStart = selStart;
- if (selEnd) spliceHints.selEnd = selEnd;
- splicesToDo.push([a + netNumLinesChangeSoFar, b - a, entries, lineAttribs, spliceHints]);
- netNumLinesChangeSoFar += (lines.length - (b - a));
- } else if (b > a) {
- splicesToDo.push([a + netNumLinesChangeSoFar, b - a, [], []]);
- }
- i++;
- }
-
- const domChanges = (splicesToDo.length > 0);
-
- for (const splice of splicesToDo) doIncorpLineSplice(...splice);
- for (const ins of domInsertsNeeded) insertDomLines(...ins);
- for (const n of toDeleteAtEnd) n.remove();
-
- // needed to stop chrome from breaking the ui when long strings without spaces are pasted
- if (scrollToTheLeftNeeded) {
- $('#innerdocbody').scrollLeft(0);
- }
-
- // if the nodes that define the selection weren't encountered during
- // content collection, figure out where those nodes are now.
- if (selection && !selStart) {
- const selStartFromHook = hooks.callAll('aceStartLineAndCharForPoint', {
- callstack: currentCallStack,
- editorInfo,
- rep,
- root: document.body,
- point: selection.startPoint,
- documentAttributeManager,
- });
- selStart = (selStartFromHook == null || selStartFromHook.length === 0)
- ? getLineAndCharForPoint(selection.startPoint) : selStartFromHook;
- }
- if (selection && !selEnd) {
- const selEndFromHook = hooks.callAll('aceEndLineAndCharForPoint', {
- callstack: currentCallStack,
- editorInfo,
- rep,
- root: document.body,
- point: selection.endPoint,
- documentAttributeManager,
- });
- selEnd = (selEndFromHook == null ||
- selEndFromHook.length === 0)
- ? getLineAndCharForPoint(selection.endPoint) : selEndFromHook;
- }
-
- // selection from content collection can, in various ways, extend past final
- // BR in firefox DOM, so cap the line
- const numLines = rep.lines.length();
- if (selStart && selStart[0] >= numLines) {
- selStart[0] = numLines - 1;
- selStart[1] = rep.lines.atIndex(selStart[0]).text.length;
- }
- if (selEnd && selEnd[0] >= numLines) {
- selEnd[0] = numLines - 1;
- selEnd[1] = rep.lines.atIndex(selEnd[0]).text.length;
- }
-
- // update rep if we have a new selection
- // NOTE: IE loses the selection when you click stuff in e.g. the
- // editbar, so removing the selection when it's lost is not a good
- // idea.
- if (selection) repSelectionChange(selStart, selEnd, selection && selection.focusAtStart);
- // update browser selection
- if (selection && (domChanges || isCaret())) {
- // if no DOM changes (not this case), want to treat range selection delicately,
- // e.g. in IE not lose which end of the selection is the focus/anchor;
- // on the other hand, we may have just noticed a press of PageUp/PageDown
- currentCallStack.selectionAffected = true;
- }
-
- currentCallStack.domClean = true;
-
- fixView();
-
- return domChanges;
- };
-
- const STYLE_ATTRIBS = {
- bold: true,
- italic: true,
- underline: true,
- strikethrough: true,
- list: true,
- };
-
- const isStyleAttribute = (aname) => !!STYLE_ATTRIBS[aname];
-
- const isDefaultLineAttribute =
- (aname) => AttributeManager.DEFAULT_LINE_ATTRIBUTES.indexOf(aname) !== -1;
-
- const insertDomLines = (nodeToAddAfter, infoStructs) => {
- let lastEntry;
- let lineStartOffset;
- for (const info of infoStructs) {
- const node = info.node;
- const key = uniqueId(node);
- let entry;
- if (lastEntry) {
- // optimization to avoid recalculation
- const next = rep.lines.next(lastEntry);
- if (next && next.key === key) {
- entry = next;
- lineStartOffset += lastEntry.width;
- }
- }
- if (!entry) {
- entry = rep.lines.atKey(key);
- lineStartOffset = rep.lines.offsetOfKey(key);
- }
- lastEntry = entry;
- getSpansForLine(entry, (tokenText, tokenClass) => {
- info.appendSpan(tokenText, tokenClass);
- }, lineStartOffset);
- info.prepareForAdd();
- entry.lineMarker = info.lineMarker;
- if (!nodeToAddAfter) {
- document.body.insertBefore(node, document.body.firstChild);
- } else {
- document.body.insertBefore(node, nodeToAddAfter.nextSibling);
- }
- nodeToAddAfter = node;
- info.notifyAdded();
- markNodeClean(node);
- }
- };
-
- const isCaret = () => (rep.selStart && rep.selEnd &&
- rep.selStart[0] === rep.selEnd[0] && rep.selStart[1] === rep.selEnd[1]);
- editorInfo.ace_isCaret = isCaret;
-
- // prereq: isCaret()
- const caretLine = () => rep.selStart[0];
-
- editorInfo.ace_caretLine = caretLine;
-
- const caretColumn = () => rep.selStart[1];
-
- editorInfo.ace_caretColumn = caretColumn;
-
- const caretDocChar = () => rep.lines.offsetOfIndex(caretLine()) + caretColumn();
-
- editorInfo.ace_caretDocChar = caretDocChar;
-
- const handleReturnIndentation = () => {
- // on return, indent to level of previous line
- if (isCaret() && caretColumn() === 0 && caretLine() > 0) {
- const lineNum = caretLine();
- const thisLine = rep.lines.atIndex(lineNum);
- const prevLine = rep.lines.prev(thisLine);
- const prevLineText = prevLine.text;
- let theIndent = /^ *(?:)/.exec(prevLineText)[0];
- const shouldIndent = parent.parent.clientVars.indentationOnNewLine;
- if (shouldIndent && /[[(:{]\s*$/.exec(prevLineText)) {
- theIndent += THE_TAB;
- }
- const cs = Changeset.builder(rep.lines.totalWidth()).keep(
- rep.lines.offsetOfIndex(lineNum), lineNum).insert(
- theIndent, [
- ['author', thisAuthor],
- ], rep.apool).toString();
- performDocumentApplyChangeset(cs);
- performSelectionChange([lineNum, theIndent.length], [lineNum, theIndent.length]);
- }
- };
-
- const getPointForLineAndChar = (lineAndChar) => {
- const line = lineAndChar[0];
- let charsLeft = lineAndChar[1];
- const lineEntry = rep.lines.atIndex(line);
- charsLeft -= lineEntry.lineMarker;
- if (charsLeft < 0) {
- charsLeft = 0;
- }
- const lineNode = lineEntry.lineNode;
- let n = lineNode;
- let after = false;
- if (charsLeft === 0) {
- return {
- node: lineNode,
- index: 0,
- maxIndex: 1,
- };
- }
- while (!(n === lineNode && after)) {
- if (after) {
- if (n.nextSibling) {
- n = n.nextSibling;
- after = false;
- } else { n = n.parentNode; }
- } else if (isNodeText(n)) {
- const len = n.nodeValue.length;
- if (charsLeft <= len) {
- return {
- node: n,
- index: charsLeft,
- maxIndex: len,
- };
- }
- charsLeft -= len;
- after = true;
- } else if (n.firstChild) { n = n.firstChild; } else { after = true; }
- }
- return {
- node: lineNode,
- index: 1,
- maxIndex: 1,
- };
- };
-
- const nodeText = (n) => n.textContent || n.nodeValue || '';
-
- const getLineAndCharForPoint = (point) => {
- // Turn DOM node selection into [line,char] selection.
- // This method has to work when the DOM is not pristine,
- // assuming the point is not in a dirty node.
- if (point.node === document.body) {
- if (point.index === 0) {
- return [0, 0];
- } else {
- const N = rep.lines.length();
- const ln = rep.lines.atIndex(N - 1);
- return [N - 1, ln.text.length];
- }
- } else {
- let n = point.node;
- let col = 0;
- // if this part fails, it probably means the selection node
- // was dirty, and we didn't see it when collecting dirty nodes.
- if (isNodeText(n)) {
- col = point.index;
- } else if (point.index > 0) {
- col = nodeText(n).length;
- }
- let parNode, prevSib;
- while ((parNode = n.parentNode) !== document.body) {
- if ((prevSib = n.previousSibling)) {
- n = prevSib;
- col += nodeText(n).length;
- } else {
- n = parNode;
- }
- }
- if (n.firstChild && isBlockElement(n.firstChild)) {
- col += 1; // lineMarker
- }
- const lineEntry = rep.lines.atKey(n.id);
- const lineNum = rep.lines.indexOfEntry(lineEntry);
- return [lineNum, col];
- }
- };
- editorInfo.ace_getLineAndCharForPoint = getLineAndCharForPoint;
-
- const createDomLineEntry = (lineString) => {
- const info = doCreateDomLine(lineString.length > 0);
- const newNode = info.node;
- return {
- key: uniqueId(newNode),
- text: lineString,
- lineNode: newNode,
- domInfo: info,
- lineMarker: 0,
- };
- };
-
- const performDocumentApplyChangeset = (changes, insertsAfterSelection) => {
- const domAndRepSplice = (startLine, deleteCount, newLineStrings) => {
- const keysToDelete = [];
- if (deleteCount > 0) {
- let entryToDelete = rep.lines.atIndex(startLine);
- for (let i = 0; i < deleteCount; i++) {
- keysToDelete.push(entryToDelete.key);
- entryToDelete = rep.lines.next(entryToDelete);
- }
- }
-
- const lineEntries = newLineStrings.map(createDomLineEntry);
-
- doRepLineSplice(startLine, deleteCount, lineEntries);
-
- let nodeToAddAfter;
- if (startLine > 0) {
- nodeToAddAfter = getCleanNodeByKey(rep.lines.atIndex(startLine - 1).key);
- } else { nodeToAddAfter = null; }
-
- insertDomLines(nodeToAddAfter, lineEntries.map((entry) => entry.domInfo));
-
- for (const k of keysToDelete) {
- const n = document.getElementById(k);
- n.parentNode.removeChild(n);
- }
-
- if (
- (rep.selStart &&
- rep.selStart[0] >= startLine &&
- rep.selStart[0] <= startLine + deleteCount) ||
- (rep.selEnd && rep.selEnd[0] >= startLine && rep.selEnd[0] <= startLine + deleteCount)) {
- currentCallStack.selectionAffected = true;
- }
- };
-
- doRepApplyChangeset(changes, insertsAfterSelection);
-
- let requiredSelectionSetting = null;
- if (rep.selStart && rep.selEnd) {
- const selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1];
- const selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1];
- const result =
- Changeset.characterRangeFollow(changes, selStartChar, selEndChar, insertsAfterSelection);
- requiredSelectionSetting = [result[0], result[1], rep.selFocusAtStart];
- }
-
- const linesMutatee = {
- splice: (start, numRemoved, ...args) => {
- domAndRepSplice(start, numRemoved, args.map((s) => s.slice(0, -1)));
- },
- get: (i) => `${rep.lines.atIndex(i).text}\n`,
- length: () => rep.lines.length(),
- };
-
- Changeset.mutateTextLines(changes, linesMutatee);
-
- if (requiredSelectionSetting) {
- performSelectionChange(
- lineAndColumnFromChar(requiredSelectionSetting[0]),
- lineAndColumnFromChar(requiredSelectionSetting[1]),
- requiredSelectionSetting[2]);
- }
- };
-
- const doRepApplyChangeset = (changes, insertsAfterSelection) => {
- Changeset.checkRep(changes);
-
- if (Changeset.oldLen(changes) !== rep.alltext.length) {
- const errMsg = `${Changeset.oldLen(changes)}/${rep.alltext.length}`;
- throw new Error(`doRepApplyChangeset length mismatch: ${errMsg}`);
- }
-
- const editEvent = currentCallStack.editEvent;
- if (editEvent.eventType === 'nonundoable') {
- if (!editEvent.changeset) {
- editEvent.changeset = changes;
- } else {
- editEvent.changeset = Changeset.compose(editEvent.changeset, changes, rep.apool);
- }
- } else {
- const inverseChangeset = Changeset.inverse(changes, {
- get: (i) => `${rep.lines.atIndex(i).text}\n`,
- length: () => rep.lines.length(),
- }, rep.alines, rep.apool);
-
- if (!editEvent.backset) {
- editEvent.backset = inverseChangeset;
- } else {
- editEvent.backset = Changeset.compose(inverseChangeset, editEvent.backset, rep.apool);
- }
- }
-
- Changeset.mutateAttributionLines(changes, rep.alines, rep.apool);
-
- if (changesetTracker.isTracking()) {
- changesetTracker.composeUserChangeset(changes);
- }
- };
-
- /**
- * Converts the position of a char (index in String) into a [row, col] tuple
- */
- const lineAndColumnFromChar = (x) => {
- const lineEntry = rep.lines.atOffset(x);
- const lineStart = rep.lines.offsetOfEntry(lineEntry);
- const lineNum = rep.lines.indexOfEntry(lineEntry);
- return [lineNum, x - lineStart];
- };
-
- const performDocumentReplaceCharRange = (startChar, endChar, newText) => {
- if (startChar === endChar && newText.length === 0) {
- return;
- }
- // Requires that the replacement preserve the property that the
- // internal document text ends in a newline. Given this, we
- // rewrite the splice so that it doesn't touch the very last
- // char of the document.
- if (endChar === rep.alltext.length) {
- if (startChar === endChar) {
- // an insert at end
- startChar--;
- endChar--;
- newText = `\n${newText.substring(0, newText.length - 1)}`;
- } else if (newText.length === 0) {
- // a delete at end
- startChar--;
- endChar--;
- } else {
- // a replace at end
- endChar--;
- newText = newText.substring(0, newText.length - 1);
- }
- }
- performDocumentReplaceRange(
- lineAndColumnFromChar(startChar), lineAndColumnFromChar(endChar), newText);
- };
-
- const performDocumentApplyAttributesToCharRange = (start, end, attribs) => {
- end = Math.min(end, rep.alltext.length - 1);
- documentAttributeManager.setAttributesOnRange(
- lineAndColumnFromChar(start), lineAndColumnFromChar(end), attribs);
- };
-
- editorInfo.ace_performDocumentApplyAttributesToCharRange =
- performDocumentApplyAttributesToCharRange;
-
- const setAttributeOnSelection = (attributeName, attributeValue) => {
- if (!(rep.selStart && rep.selEnd)) return;
-
- documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [
- [attributeName, attributeValue],
- ]);
- };
- editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection;
-
- const getAttributeOnSelection = (attributeName, prevChar) => {
- if (!(rep.selStart && rep.selEnd)) return;
- const isNotSelection = (rep.selStart[0] === rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
- if (isNotSelection) {
- if (prevChar) {
- // If it's not the start of the line
- if (rep.selStart[1] !== 0) {
- rep.selStart[1]--;
- }
- }
- }
-
- const withIt = new AttributeMap(rep.apool).set(attributeName, 'true').toString();
- const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
- const hasIt = (attribs) => withItRegex.test(attribs);
-
- const rangeHasAttrib = (selStart, selEnd) => {
- // if range is collapsed -> no attribs in range
- if (selStart[1] === selEnd[1] && selStart[0] === selEnd[0]) return false;
-
- if (selStart[0] !== selEnd[0]) { // -> More than one line selected
- let hasAttrib = true;
-
- // from selStart to the end of the first line
- hasAttrib = hasAttrib &&
- rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
-
- // for all lines in between
- for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
- hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
- }
-
- // for the last, potentially partial, line
- hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
-
- return hasAttrib;
- }
-
- // Logic tells us we now have a range on a single line
-
- const lineNum = selStart[0];
- const start = selStart[1];
- const end = selEnd[1];
- let hasAttrib = true;
-
- let indexIntoLine = 0;
- for (const op of Changeset.deserializeOps(rep.alines[lineNum])) {
- const opStartInLine = indexIntoLine;
- const opEndInLine = opStartInLine + op.chars;
- if (!hasIt(op.attribs)) {
- // does op overlap selection?
- if (!(opEndInLine <= start || opStartInLine >= end)) {
- // since it's overlapping but hasn't got the attrib -> range hasn't got it
- hasAttrib = false;
- break;
- }
- }
- indexIntoLine = opEndInLine;
- }
-
- return hasAttrib;
- };
- return rangeHasAttrib(rep.selStart, rep.selEnd);
- };
-
- editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection;
-
- const toggleAttributeOnSelection = (attributeName) => {
- if (!(rep.selStart && rep.selEnd)) return;
-
- let selectionAllHasIt = true;
- const withIt = new AttributeMap(rep.apool).set(attributeName, 'true').toString();
- const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
-
- const hasIt = (attribs) => withItRegex.test(attribs);
-
- const selStartLine = rep.selStart[0];
- const selEndLine = rep.selEnd[0];
- for (let n = selStartLine; n <= selEndLine; n++) {
- let indexIntoLine = 0;
- let selectionStartInLine = 0;
- if (documentAttributeManager.lineHasMarker(n)) {
- selectionStartInLine = 1; // ignore "*" used as line marker
- }
- let selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline
- if (n === selStartLine) {
- selectionStartInLine = rep.selStart[1];
- }
- if (n === selEndLine) {
- selectionEndInLine = rep.selEnd[1];
- }
- for (const op of Changeset.deserializeOps(rep.alines[n])) {
- const opStartInLine = indexIntoLine;
- const opEndInLine = opStartInLine + op.chars;
- if (!hasIt(op.attribs)) {
- // does op overlap selection?
- if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) {
- selectionAllHasIt = false;
- break;
- }
- }
- indexIntoLine = opEndInLine;
- }
- if (!selectionAllHasIt) {
- break;
- }
- }
-
-
- const attributeValue = selectionAllHasIt ? '' : 'true';
- documentAttributeManager.setAttributesOnRange(
- rep.selStart, rep.selEnd, [[attributeName, attributeValue]]);
- if (attribIsFormattingStyle(attributeName)) {
- updateStyleButtonState(attributeName, !selectionAllHasIt); // italic, bold, ...
- }
- };
- editorInfo.ace_toggleAttributeOnSelection = toggleAttributeOnSelection;
-
- const performDocumentReplaceSelection = (newText) => {
- if (!(rep.selStart && rep.selEnd)) return;
- performDocumentReplaceRange(rep.selStart, rep.selEnd, newText);
- };
-
- // Change the abstract representation of the document to have a different set of lines.
- // Must be called after rep.alltext is set.
- const doRepLineSplice = (startLine, deleteCount, newLineEntries) => {
- for (const entry of newLineEntries) entry.width = entry.text.length + 1;
-
- const startOldChar = rep.lines.offsetOfIndex(startLine);
- const endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount);
-
- rep.lines.splice(startLine, deleteCount, newLineEntries);
- currentCallStack.docTextChanged = true;
- currentCallStack.repChanged = true;
- const newText = newLineEntries.map((e) => `${e.text}\n`).join('');
-
- rep.alltext = rep.alltext.substring(0, startOldChar) +
- newText + rep.alltext.substring(endOldChar, rep.alltext.length);
- };
-
- const doIncorpLineSplice = (startLine, deleteCount, newLineEntries, lineAttribs, hints) => {
- const startOldChar = rep.lines.offsetOfIndex(startLine);
- const endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount);
-
- const oldRegionStart = rep.lines.offsetOfIndex(startLine);
-
- let selStartHintChar, selEndHintChar;
- if (hints && hints.selStart) {
- selStartHintChar =
- rep.lines.offsetOfIndex(hints.selStart[0]) + hints.selStart[1] - oldRegionStart;
- }
- if (hints && hints.selEnd) {
- selEndHintChar = rep.lines.offsetOfIndex(hints.selEnd[0]) + hints.selEnd[1] - oldRegionStart;
- }
-
- const newText = newLineEntries.map((e) => `${e.text}\n`).join('');
- const oldText = rep.alltext.substring(startOldChar, endOldChar);
- const oldAttribs = rep.alines.slice(startLine, startLine + deleteCount).join('');
- const newAttribs = `${lineAttribs.join('|1+1')}|1+1`; // not valid in a changeset
- const analysis =
- analyzeChange(oldText, newText, oldAttribs, newAttribs, selStartHintChar, selEndHintChar);
- const commonStart = analysis[0];
- let commonEnd = analysis[1];
- let shortOldText = oldText.substring(commonStart, oldText.length - commonEnd);
- let shortNewText = newText.substring(commonStart, newText.length - commonEnd);
- let spliceStart = startOldChar + commonStart;
- let spliceEnd = endOldChar - commonEnd;
- let shiftFinalNewlineToBeforeNewText = false;
-
- // adjust the splice to not involve the final newline of the document;
- // be very defensive
- if (shortOldText.charAt(shortOldText.length - 1) === '\n' &&
- shortNewText.charAt(shortNewText.length - 1) === '\n') {
- // replacing text that ends in newline with text that also ends in newline
- // (still, after analysis, somehow)
- shortOldText = shortOldText.slice(0, -1);
- shortNewText = shortNewText.slice(0, -1);
- spliceEnd--;
- commonEnd++;
- }
- if (shortOldText.length === 0 &&
- spliceStart === rep.alltext.length &&
- shortNewText.length > 0) {
- // inserting after final newline, bad
- spliceStart--;
- spliceEnd--;
- shortNewText = `\n${shortNewText.slice(0, -1)}`;
- shiftFinalNewlineToBeforeNewText = true;
- }
- if (spliceEnd === rep.alltext.length &&
- shortOldText.length > 0 &&
- shortNewText.length === 0) {
- // deletion at end of rep.alltext
- if (rep.alltext.charAt(spliceStart - 1) === '\n') {
- // (if not then what the heck? it will definitely lead
- // to a rep.alltext without a final newline)
- spliceStart--;
- spliceEnd--;
- }
- }
-
- if (!(shortOldText.length === 0 && shortNewText.length === 0)) {
- const oldDocText = rep.alltext;
- const oldLen = oldDocText.length;
-
- const spliceStartLine = rep.lines.indexOfOffset(spliceStart);
- const spliceStartLineStart = rep.lines.offsetOfIndex(spliceStartLine);
-
- const startBuilder = () => {
- const builder = Changeset.builder(oldLen);
- builder.keep(spliceStartLineStart, spliceStartLine);
- builder.keep(spliceStart - spliceStartLineStart);
- return builder;
- };
-
- const eachAttribRun = (attribs, func /* (startInNewText, endInNewText, attribs)*/) => {
- let textIndex = 0;
- const newTextStart = commonStart;
- const newTextEnd = newText.length - commonEnd - (shiftFinalNewlineToBeforeNewText ? 1 : 0);
- for (const op of Changeset.deserializeOps(attribs)) {
- const nextIndex = textIndex + op.chars;
- if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
- func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
- }
- textIndex = nextIndex;
- }
- };
-
- const justApplyStyles = (shortNewText === shortOldText);
- let theChangeset;
-
- if (justApplyStyles) {
- // create changeset that clears the incorporated styles on
- // the existing text. we compose this with the
- // changeset the applies the styles found in the DOM.
- // This allows us to incorporate, e.g., Safari's native "unbold".
- const incorpedAttribClearer = cachedStrFunc(
- (oldAtts) => Changeset.mapAttribNumbers(oldAtts, (n) => {
- const k = rep.apool.getAttribKey(n);
- if (isStyleAttribute(k)) {
- return rep.apool.putAttrib([k, '']);
- }
- return false;
- }));
-
- const builder1 = startBuilder();
- if (shiftFinalNewlineToBeforeNewText) {
- builder1.keep(1, 1);
- }
- eachAttribRun(oldAttribs, (start, end, attribs) => {
- builder1.keepText(newText.substring(start, end), incorpedAttribClearer(attribs));
- });
- const clearer = builder1.toString();
-
- const builder2 = startBuilder();
- if (shiftFinalNewlineToBeforeNewText) {
- builder2.keep(1, 1);
- }
- eachAttribRun(newAttribs, (start, end, attribs) => {
- builder2.keepText(newText.substring(start, end), attribs);
- });
- const styler = builder2.toString();
-
- theChangeset = Changeset.compose(clearer, styler, rep.apool);
- } else {
- const builder = startBuilder();
-
- const spliceEndLine = rep.lines.indexOfOffset(spliceEnd);
- const spliceEndLineStart = rep.lines.offsetOfIndex(spliceEndLine);
- if (spliceEndLineStart > spliceStart) {
- builder.remove(spliceEndLineStart - spliceStart, spliceEndLine - spliceStartLine);
- builder.remove(spliceEnd - spliceEndLineStart);
- } else {
- builder.remove(spliceEnd - spliceStart);
- }
-
- let isNewTextMultiauthor = false;
- const authorizer = cachedStrFunc((oldAtts) => {
- const attribs = AttributeMap.fromString(oldAtts, rep.apool);
- if (!isNewTextMultiauthor || !attribs.has('author')) attribs.set('author', thisAuthor);
- return attribs.toString();
- });
-
- let foundDomAuthor = '';
- eachAttribRun(newAttribs, (start, end, attribs) => {
- const a = AttributeMap.fromString(attribs, rep.apool).get('author');
- if (a && a !== foundDomAuthor) {
- if (!foundDomAuthor) {
- foundDomAuthor = a;
- } else {
- isNewTextMultiauthor = true; // multiple authors in DOM!
- }
- }
- });
-
- if (shiftFinalNewlineToBeforeNewText) {
- builder.insert('\n', authorizer(''));
- }
-
- eachAttribRun(newAttribs, (start, end, attribs) => {
- builder.insert(newText.substring(start, end), authorizer(attribs));
- });
- theChangeset = builder.toString();
- }
-
- doRepApplyChangeset(theChangeset);
- }
-
- // do this no matter what, because we need to get the right
- // line keys into the rep.
- doRepLineSplice(startLine, deleteCount, newLineEntries);
- };
-
- const cachedStrFunc = (func) => {
- const cache = {};
- return (s) => {
- if (!cache[s]) {
- cache[s] = func(s);
- }
- return cache[s];
- };
- };
-
- const analyzeChange = (
- oldText, newText, oldAttribs, newAttribs, optSelStartHint, optSelEndHint) => {
- // we need to take into account both the styles attributes & attributes defined by
- // the plugins, so basically we can ignore only the default line attribs used by
- // Etherpad
- const incorpedAttribFilter = (anum) => !isDefaultLineAttribute(rep.apool.getAttribKey(anum));
-
- const attribRuns = (attribs) => {
- const lengs = [];
- const atts = [];
- for (const op of Changeset.deserializeOps(attribs)) {
- lengs.push(op.chars);
- atts.push(op.attribs);
- }
- return [lengs, atts];
- };
-
- const attribIterator = (runs, backward) => {
- const lengs = runs[0];
- const atts = runs[1];
- let i = (backward ? lengs.length - 1 : 0);
- let j = 0;
- const next = () => {
- while (j >= lengs[i]) {
- if (backward) i--;
- else i++;
- j = 0;
- }
- const a = atts[i];
- j++;
- return a;
- };
- return next;
- };
-
- const oldLen = oldText.length;
- const newLen = newText.length;
- const minLen = Math.min(oldLen, newLen);
-
- const oldARuns = attribRuns(Changeset.filterAttribNumbers(oldAttribs, incorpedAttribFilter));
- const newARuns = attribRuns(Changeset.filterAttribNumbers(newAttribs, incorpedAttribFilter));
-
- let commonStart = 0;
- const oldStartIter = attribIterator(oldARuns, false);
- const newStartIter = attribIterator(newARuns, false);
- while (commonStart < minLen) {
- if (oldText.charAt(commonStart) === newText.charAt(commonStart) &&
- oldStartIter() === newStartIter()) {
- commonStart++;
- } else { break; }
- }
-
- let commonEnd = 0;
- const oldEndIter = attribIterator(oldARuns, true);
- const newEndIter = attribIterator(newARuns, true);
- while (commonEnd < minLen) {
- if (commonEnd === 0) {
- // assume newline in common
- oldEndIter();
- newEndIter();
- commonEnd++;
- } else if (
- oldText.charAt(oldLen - 1 - commonEnd) === newText.charAt(newLen - 1 - commonEnd) &&
- oldEndIter() === newEndIter()) {
- commonEnd++;
- } else { break; }
- }
-
- let hintedCommonEnd = -1;
- if ((typeof optSelEndHint) === 'number') {
- hintedCommonEnd = newLen - optSelEndHint;
- }
-
-
- if (commonStart + commonEnd > oldLen) {
- // ambiguous insertion
- const minCommonEnd = oldLen - commonStart;
- const maxCommonEnd = commonEnd;
- if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) {
- commonEnd = hintedCommonEnd;
- } else {
- commonEnd = minCommonEnd;
- }
- commonStart = oldLen - commonEnd;
- }
- if (commonStart + commonEnd > newLen) {
- // ambiguous deletion
- const minCommonEnd = newLen - commonStart;
- const maxCommonEnd = commonEnd;
- if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) {
- commonEnd = hintedCommonEnd;
- } else {
- commonEnd = minCommonEnd;
- }
- commonStart = newLen - commonEnd;
- }
-
- return [commonStart, commonEnd];
- };
-
- const equalLineAndChars = (a, b) => {
- if (!a) return !b;
- if (!b) return !a;
- return (a[0] === b[0] && a[1] === b[1]);
- };
-
- const performSelectionChange = (selectStart, selectEnd, focusAtStart) => {
- if (repSelectionChange(selectStart, selectEnd, focusAtStart)) {
- currentCallStack.selectionAffected = true;
- }
- };
- editorInfo.ace_performSelectionChange = performSelectionChange;
-
- // Change the abstract representation of the document to have a different selection.
- // Should not rely on the line representation. Should not affect the DOM.
-
-
- const repSelectionChange = (selectStart, selectEnd, focusAtStart) => {
- focusAtStart = !!focusAtStart;
-
- const newSelFocusAtStart = (focusAtStart && ((!selectStart) ||
- (!selectEnd) ||
- (selectStart[0] !== selectEnd[0]) ||
- (selectStart[1] !== selectEnd[1])));
-
- if ((!equalLineAndChars(rep.selStart, selectStart)) ||
- (!equalLineAndChars(rep.selEnd, selectEnd)) ||
- (rep.selFocusAtStart !== newSelFocusAtStart)) {
- rep.selStart = selectStart;
- rep.selEnd = selectEnd;
- rep.selFocusAtStart = newSelFocusAtStart;
- currentCallStack.repChanged = true;
-
- // select the formatting buttons when there is the style applied on selection
- selectFormattingButtonIfLineHasStyleApplied(rep);
-
- hooks.callAll('aceSelectionChanged', {
- rep,
- callstack: currentCallStack,
- documentAttributeManager,
- });
-
- // we scroll when user places the caret at the last line of the pad
- // when this settings is enabled
- const docTextChanged = currentCallStack.docTextChanged;
- if (!docTextChanged) {
- const isScrollableEvent = !isPadLoading(currentCallStack.type) &&
- isScrollableEditEvent(currentCallStack.type);
- const innerHeight = getInnerHeight();
- scroll.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(
- rep, isScrollableEvent, innerHeight * 2);
- }
-
- return true;
- }
- return false;
- };
-
- const isPadLoading = (t) => t === 'setup' || t === 'setBaseText' || t === 'importText';
-
- const updateStyleButtonState = (attribName, hasStyleOnRepSelection) => {
- const $formattingButton = parent.parent.$(`[data-key="${attribName}"]`).find('a');
- $formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection);
- };
-
- const attribIsFormattingStyle = (attribName) => FORMATTING_STYLES.indexOf(attribName) !== -1;
-
- const selectFormattingButtonIfLineHasStyleApplied = (rep) => {
- for (const style of FORMATTING_STYLES) {
- const hasStyleOnRepSelection =
- documentAttributeManager.hasAttributeOnSelectionOrCaretPosition(style);
- updateStyleButtonState(style, hasStyleOnRepSelection);
- }
- };
-
- const doCreateDomLine =
- (nonEmpty) => domline.createDomLine(nonEmpty, doesWrap, browser, document);
-
- const textify =
- (str) => str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' ');
-
- const _blockElems = {
- div: 1,
- p: 1,
- pre: 1,
- li: 1,
- ol: 1,
- ul: 1,
- };
-
- for (const element of hooks.callAll('aceRegisterBlockElements')) _blockElems[element] = 1;
-
- const isBlockElement = (n) => !!_blockElems[(n.tagName || '').toLowerCase()];
- editorInfo.ace_isBlockElement = isBlockElement;
-
- const getDirtyRanges = () => {
- // based on observedChanges, return a list of ranges of original lines
- // that need to be removed or replaced with new user content to incorporate
- // the user's changes into the line representation. ranges may be zero-length,
- // indicating inserted content. for example, [0,0] means content was inserted
- // at the top of the document, while [3,4] means line 3 was deleted, modified,
- // or replaced with one or more new lines of content. ranges do not touch.
-
- const cleanNodeForIndexCache = {};
- const N = rep.lines.length(); // old number of lines
-
-
- const cleanNodeForIndex = (i) => {
- // if line (i) in the un-updated line representation maps to a clean node
- // in the document, return that node.
- // if (i) is out of bounds, return true. else return false.
- if (cleanNodeForIndexCache[i] === undefined) {
- let result;
- if (i < 0 || i >= N) {
- result = true; // truthy, but no actual node
- } else {
- const key = rep.lines.atIndex(i).key;
- result = (getCleanNodeByKey(key) || false);
- }
- cleanNodeForIndexCache[i] = result;
- }
- return cleanNodeForIndexCache[i];
- };
- const isConsecutiveCache = {};
-
- const isConsecutive = (i) => {
- if (isConsecutiveCache[i] === undefined) {
- isConsecutiveCache[i] = (() => {
- // returns whether line (i) and line (i-1), assumed to be map to clean DOM nodes,
- // or document boundaries, are consecutive in the changed DOM
- const a = cleanNodeForIndex(i - 1);
- const b = cleanNodeForIndex(i);
- if ((!a) || (!b)) return false; // violates precondition
- if ((a === true) && (b === true)) return !document.body.firstChild;
- if ((a === true) && b.previousSibling) return false;
- if ((b === true) && a.nextSibling) return false;
- if ((a === true) || (b === true)) return true;
- return a.nextSibling === b;
- })();
- }
- return isConsecutiveCache[i];
- };
-
- // returns whether line (i) in the un-updated representation maps to a clean node,
- // or is outside the bounds of the document
- const isClean = (i) => !!cleanNodeForIndex(i);
-
- // list of pairs, each representing a range of lines that is clean and consecutive
- // in the changed DOM. lines (-1) and (N) are always clean, but may or may not
- // be consecutive with lines in the document. pairs are in sorted order.
- const cleanRanges = [
- [-1, N + 1],
- ];
-
- // returns index of cleanRange containing i, or -1 if none
- const rangeForLine = (i) => {
- for (const [idx, r] of cleanRanges.entries()) {
- if (i < r[0]) return -1;
- if (i < r[1]) return idx;
- }
- return -1;
- };
-
- const removeLineFromRange = (rng, line) => {
- // rng is index into cleanRanges, line is line number
- // precond: line is in rng
- const a = cleanRanges[rng][0];
- const b = cleanRanges[rng][1];
- if ((a + 1) === b) cleanRanges.splice(rng, 1);
- else if (line === a) cleanRanges[rng][0]++;
- else if (line === (b - 1)) cleanRanges[rng][1]--;
- else cleanRanges.splice(rng, 1, [a, line], [line + 1, b]);
- };
-
- const splitRange = (rng, pt) => {
- // precond: pt splits cleanRanges[rng] into two non-empty ranges
- const a = cleanRanges[rng][0];
- const b = cleanRanges[rng][1];
- cleanRanges.splice(rng, 1, [a, pt], [pt, b]);
- };
-
- const correctedLines = {};
-
- const correctlyAssignLine = (line) => {
- if (correctedLines[line]) return true;
- correctedLines[line] = true;
- // "line" is an index of a line in the un-updated rep.
- // returns whether line was already correctly assigned (i.e. correctly
- // clean or dirty, according to cleanRanges, and if clean, correctly
- // attached or not attached (i.e. in the same range as) the prev and next lines).
- const rng = rangeForLine(line);
- const lineClean = isClean(line);
- if (rng < 0) {
- if (lineClean) {
- // somehow lost clean line
- }
- return true;
- }
- if (!lineClean) {
- // a clean-range includes this dirty line, fix it
- removeLineFromRange(rng, line);
- return false;
- } else {
- // line is clean, but could be wrongly connected to a clean line
- // above or below
- const a = cleanRanges[rng][0];
- const b = cleanRanges[rng][1];
- let didSomething = false;
- // we'll leave non-clean adjacent nodes in the clean range for the caller to
- // detect and deal with. we deal with whether the range should be split
- // just above or just below this line.
- if (a < line && isClean(line - 1) && !isConsecutive(line)) {
- splitRange(rng, line);
- didSomething = true;
- }
- if (b > (line + 1) && isClean(line + 1) && !isConsecutive(line + 1)) {
- splitRange(rng, line + 1);
- didSomething = true;
- }
- return !didSomething;
- }
- };
-
- const detectChangesAroundLine = (line, reqInARow) => {
- // make sure cleanRanges is correct about line number "line" and the surrounding
- // lines; only stops checking at end of document or after no changes need
- // making for several consecutive lines. note that iteration is over old lines,
- // so this operation takes time proportional to the number of old lines
- // that are changed or missing, not the number of new lines inserted.
- let correctInARow = 0;
- let currentIndex = line;
- while (correctInARow < reqInARow && currentIndex >= 0) {
- if (correctlyAssignLine(currentIndex)) {
- correctInARow++;
- } else { correctInARow = 0; }
- currentIndex--;
- }
- correctInARow = 0;
- currentIndex = line;
- while (correctInARow < reqInARow && currentIndex < N) {
- if (correctlyAssignLine(currentIndex)) {
- correctInARow++;
- } else { correctInARow = 0; }
- currentIndex++;
- }
- };
-
- if (N === 0) {
- if (!isConsecutive(0)) {
- splitRange(0, 0);
- }
- } else {
- detectChangesAroundLine(0, 1);
- detectChangesAroundLine(N - 1, 1);
-
- for (const k of Object.keys(observedChanges.cleanNodesNearChanges)) {
- const key = k.substring(1);
- if (rep.lines.containsKey(key)) {
- const line = rep.lines.indexOfKey(key);
- detectChangesAroundLine(line, 2);
- }
- }
- }
-
- const dirtyRanges = [];
- for (let r = 0; r < cleanRanges.length - 1; r++) {
- dirtyRanges.push([cleanRanges[r][1], cleanRanges[r + 1][0]]);
- }
-
- return dirtyRanges;
- };
-
- const markNodeClean = (n) => {
- // clean nodes have knownHTML that matches their innerHTML
- setAssoc(n, 'dirtiness', {nodeId: uniqueId(n), knownHTML: n.innerHTML});
- };
-
- const isNodeDirty = (n) => {
- if (n.parentNode !== document.body) return true;
- const data = getAssoc(n, 'dirtiness');
- if (!data) return true;
- if (n.id !== data.nodeId) return true;
- if (n.innerHTML !== data.knownHTML) return true;
- return false;
- };
-
- const handleClick = (evt) => {
- inCallStackIfNecessary('handleClick', () => {
- idleWorkTimer.atMost(200);
- });
-
- const isLink = (n) => (n.tagName || '').toLowerCase() === 'a' && n.href;
-
- // only want to catch left-click
- if ((!evt.ctrlKey) && (evt.button !== 2) && (evt.button !== 3)) {
- // find A tag with HREF
- let n = evt.target;
- while (n && n.parentNode && !isLink(n)) {
- n = n.parentNode;
- }
- if (n && isLink(n)) {
- try {
- window.open(n.href, '_blank', 'noopener,noreferrer');
- } catch (e) {
- // absorb "user canceled" error in IE for certain prompts
- }
- evt.preventDefault();
- }
- }
-
- hideEditBarDropdowns();
- };
-
- const hideEditBarDropdowns = () => {
- window.parent.parent.padeditbar.toggleDropDown('none');
- };
-
- const renumberList = (lineNum) => {
- // 1-check we are in a list
- let type = getLineListType(lineNum);
- if (!type) {
- return null;
- }
- type = /([a-z]+)[0-9]+/.exec(type);
- if (type[1] === 'indent') {
- return null;
- }
-
- // 2-find the first line of the list
- while (lineNum - 1 >= 0 && (type = getLineListType(lineNum - 1))) {
- type = /([a-z]+)[0-9]+/.exec(type);
- if (type[1] === 'indent') break;
- lineNum--;
- }
-
- // 3-renumber every list item of the same level from the beginning, level 1
- // IMPORTANT: never skip a level because there imbrication may be arbitrary
- const builder = Changeset.builder(rep.lines.totalWidth());
- let loc = [0, 0];
- const applyNumberList = (line, level) => {
- // init
- let position = 1;
- let curLevel = level;
- let listType;
- // loop over the lines
- while ((listType = getLineListType(line))) {
- // apply new num
- listType = /([a-z]+)([0-9]+)/.exec(listType);
- curLevel = Number(listType[2]);
- if (isNaN(curLevel) || listType[0] === 'indent') {
- return line;
- } else if (curLevel === level) {
- ChangesetUtils.buildKeepRange(rep, builder, loc, (loc = [line, 0]));
- ChangesetUtils.buildKeepRange(rep, builder, loc, (loc = [line, 1]), [
- ['start', position],
- ], rep.apool);
-
- position++;
- line++;
- } else if (curLevel < level) {
- return line;// back to parent
- } else {
- line = applyNumberList(line, level + 1);// recursive call
- }
- }
- return line;
- };
-
- applyNumberList(lineNum, 1);
- const cs = builder.toString();
- if (!Changeset.isIdentity(cs)) {
- performDocumentApplyChangeset(cs);
- }
-
- // 4-apply the modifications
- };
- editorInfo.ace_renumberList = renumberList;
-
- const setLineListType = (lineNum, listType) => {
- if (listType === '') {
- documentAttributeManager.removeAttributeOnLine(lineNum, listAttributeName);
- documentAttributeManager.removeAttributeOnLine(lineNum, 'start');
- } else {
- documentAttributeManager.setAttributeOnLine(lineNum, listAttributeName, listType);
- }
-
- // if the list has been removed, it is necessary to renumber
- // starting from the *next* line because the list may have been
- // separated. If it returns null, it means that the list was not cut, try
- // from the current one.
- if (renumberList(lineNum + 1) == null) {
- renumberList(lineNum);
- }
- };
-
- const doReturnKey = () => {
- if (!(rep.selStart && rep.selEnd)) {
- return;
- }
-
- const lineNum = rep.selStart[0];
- let listType = getLineListType(lineNum);
-
- if (listType) {
- const text = rep.lines.atIndex(lineNum).text;
- listType = /([a-z]+)([0-9]+)/.exec(listType);
- const type = listType[1];
- const level = Number(listType[2]);
-
- // detect empty list item; exclude indentation
- if (text === '*' && type !== 'indent') {
- // if not already on the highest level
- if (level > 1) {
- setLineListType(lineNum, type + (level - 1));// automatically decrease the level
- } else {
- setLineListType(lineNum, '');// remove the list
- renumberList(lineNum + 1);// trigger renumbering of list that may be right after
- }
- } else if (lineNum + 1 <= rep.lines.length()) {
- performDocumentReplaceSelection('\n');
- setLineListType(lineNum + 1, type + level);
- }
- } else {
- performDocumentReplaceSelection('\n');
- handleReturnIndentation();
- }
- };
- editorInfo.ace_doReturnKey = doReturnKey;
-
- const doIndentOutdent = (isOut) => {
- if (!((rep.selStart && rep.selEnd) ||
- (rep.selStart[0] === rep.selEnd[0] &&
- rep.selStart[1] === rep.selEnd[1] &&
- rep.selEnd[1] > 1)) &&
- isOut !== true) {
- return false;
- }
-
- const firstLine = rep.selStart[0];
- const lastLine = Math.max(firstLine, rep.selEnd[0] - ((rep.selEnd[1] === 0) ? 1 : 0));
- const mods = [];
- for (let n = firstLine; n <= lastLine; n++) {
- let listType = getLineListType(n);
- let t = 'indent';
- let level = 0;
- if (listType) {
- listType = /([a-z]+)([0-9]+)/.exec(listType);
- if (listType) {
- t = listType[1];
- level = Number(listType[2]);
- }
- }
- const newLevel = Math.max(0, Math.min(MAX_LIST_LEVEL, level + (isOut ? -1 : 1)));
- if (level !== newLevel) {
- mods.push([n, (newLevel > 0) ? t + newLevel : '']);
- }
- }
-
- for (const mod of mods) setLineListType(mod[0], mod[1]);
- return true;
- };
- editorInfo.ace_doIndentOutdent = doIndentOutdent;
-
- const doTabKey = (shiftDown) => {
- if (!doIndentOutdent(shiftDown)) {
- performDocumentReplaceSelection(THE_TAB);
- }
- };
-
- const doDeleteKey = (optEvt) => {
- const evt = optEvt || {};
- let handled = false;
- if (rep.selStart) {
- if (isCaret()) {
- const lineNum = caretLine();
- const col = caretColumn();
- const lineEntry = rep.lines.atIndex(lineNum);
- const lineText = lineEntry.text;
- const lineMarker = lineEntry.lineMarker;
- if (evt.metaKey && col > lineMarker) {
- // cmd-backspace deletes to start of line (if not already at start)
- performDocumentReplaceRange([lineNum, lineMarker], [lineNum, col], '');
- handled = true;
- } else if (/^ +$/.exec(lineText.substring(lineMarker, col))) {
- const col2 = col - lineMarker;
- const tabSize = THE_TAB.length;
- const toDelete = ((col2 - 1) % tabSize) + 1;
- performDocumentReplaceRange([lineNum, col - toDelete], [lineNum, col], '');
- handled = true;
- }
- }
- if (!handled) {
- if (isCaret()) {
- const theLine = caretLine();
- const lineEntry = rep.lines.atIndex(theLine);
- if (caretColumn() <= lineEntry.lineMarker) {
- // delete at beginning of line
- const prevLineListType = (theLine > 0 ? getLineListType(theLine - 1) : '');
- const thisLineListType = getLineListType(theLine);
- const prevLineEntry = (theLine > 0 && rep.lines.atIndex(theLine - 1));
- const prevLineBlank = (prevLineEntry &&
- prevLineEntry.text.length === prevLineEntry.lineMarker);
-
- const thisLineHasMarker = documentAttributeManager.lineHasMarker(theLine);
-
- if (thisLineListType) {
- // this line is a list
- if (prevLineBlank && !prevLineListType) {
- // previous line is blank, remove it
- performDocumentReplaceRange(
- [theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
- } else {
- // delistify
- performDocumentReplaceRange([theLine, 0], [theLine, lineEntry.lineMarker], '');
- }
- } else if (thisLineHasMarker && prevLineEntry) {
- // If the line has any attributes assigned, remove them by removing the marker '*'
- performDocumentReplaceRange(
- [theLine - 1, prevLineEntry.text.length], [theLine, lineEntry.lineMarker], '');
- } else if (theLine > 0) {
- // remove newline
- performDocumentReplaceRange(
- [theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
- }
- } else {
- const docChar = caretDocChar();
- if (docChar > 0) {
- if (evt.metaKey || evt.ctrlKey || evt.altKey) {
- // delete as many unicode "letters or digits" in a row as possible;
- // always delete one char, delete further even if that first char
- // isn't actually a word char.
- let deleteBackTo = docChar - 1;
- while (deleteBackTo > lineEntry.lineMarker &&
- isWordChar(rep.alltext.charAt(deleteBackTo - 1))) {
- deleteBackTo--;
- }
- performDocumentReplaceCharRange(deleteBackTo, docChar, '');
- } else {
- // normal delete
- performDocumentReplaceCharRange(docChar - 1, docChar, '');
- }
- }
- }
- } else {
- performDocumentReplaceSelection('');
- }
- }
- }
- // if the list has been removed, it is necessary to renumber
- // starting from the *next* line because the list may have been
- // separated. If it returns null, it means that the list was not cut, try
- // from the current one.
- const line = caretLine();
- if (line !== -1 && renumberList(line + 1) == null) {
- renumberList(line);
- }
- };
-
- const isWordChar = (c) => padutils.wordCharRegex.test(c);
- editorInfo.ace_isWordChar = isWordChar;
-
- const handleKeyEvent = (evt) => {
- if (!isEditable) return;
- const {type, charCode, keyCode, which, altKey, shiftKey} = evt;
-
- // Don't take action based on modifier keys going up and down.
- // Modifier keys do not generate "keypress" events.
- // 224 is the command-key under Mac Firefox.
- // 91 is the Windows key in IE; it is ASCII for open-bracket but isn't the keycode for that key
- // 20 is capslock in IE.
- const isModKey = !charCode && (type === 'keyup' || type === 'keydown') &&
- (keyCode === 16 || keyCode === 17 || keyCode === 18 ||
- keyCode === 20 || keyCode === 224 || keyCode === 91);
- if (isModKey) return;
-
- // If the key is a keypress and the browser is opera and the key is enter,
- // do nothign at all as this fires twice.
- if (keyCode === 13 && browser.opera && type === 'keypress') {
- // This stops double enters in Opera but double Tabs still show on single
- // tab keypress, adding keyCode == 9 to this doesn't help as the event is fired twice
- return;
- }
-
- const isTypeForSpecialKey = browser.safari || browser.chrome || browser.firefox
- ? type === 'keydown' : type === 'keypress';
- const isTypeForCmdKey = browser.safari || browser.chrome || browser.firefox
- ? type === 'keydown' : type === 'keypress';
-
- let stopped = false;
-
- inCallStackIfNecessary('handleKeyEvent', function () {
- if (type === 'keypress' || (isTypeForSpecialKey && keyCode === 13 /* return*/)) {
- // in IE, special keys don't send keypress, the keydown does the action
- if (!outsideKeyPress(evt)) {
- evt.preventDefault();
- stopped = true;
- }
- } else if (evt.key === 'Dead') {
- // If it's a dead key we don't want to do any Etherpad behavior.
- stopped = true;
- return true;
- } else if (type === 'keydown') {
- outsideKeyDown(evt);
- }
- let specialHandled = false;
- if (!stopped) {
- const specialHandledInHook = hooks.callAll('aceKeyEvent', {
- callstack: currentCallStack,
- editorInfo,
- rep,
- documentAttributeManager,
- evt,
- });
-
- // if any hook returned true, set specialHandled with true
- if (specialHandledInHook) {
- specialHandled = specialHandledInHook.indexOf(true) !== -1;
- }
-
- const padShortcutEnabled = parent.parent.clientVars.padShortcutEnabled;
- if (!specialHandled && isTypeForSpecialKey &&
- altKey && keyCode === 120 &&
- padShortcutEnabled.altF9) {
- // Alt F9 focuses on the File Menu and/or editbar.
- // Note that while most editors use Alt F10 this is not desirable
- // As ubuntu cannot use Alt F10....
- // Focus on the editbar.
- // -- TODO: Move Focus back to previous state (we know it so we can use it)
- const firstEditbarElement = parent.parent.$('#editbar')
- .children('ul').first().children().first()
- .children().first().children().first();
- $(this).blur();
- firstEditbarElement.focus();
- evt.preventDefault();
- }
- if (!specialHandled && type === 'keydown' &&
- altKey && keyCode === 67 &&
- padShortcutEnabled.altC) {
- // Alt c focuses on the Chat window
- $(this).blur();
- parent.parent.chat.show();
- parent.parent.$('#chatinput').focus();
- evt.preventDefault();
- }
- if (!specialHandled && type === 'keydown' &&
- evt.ctrlKey && shiftKey && keyCode === 50 &&
- padShortcutEnabled.cmdShift2) {
- // Control-Shift-2 shows a gritter popup showing a line author
- const lineNumber = rep.selEnd[0];
- const alineAttrs = rep.alines[lineNumber];
- const apool = rep.apool;
-
- // TODO: support selection ranges
- // TODO: Still work when authorship colors have been cleared
- // TODO: i18n
- // TODO: There appears to be a race condition or so.
- const authorIds = new Set();
- if (alineAttrs) {
- for (const op of Changeset.deserializeOps(alineAttrs)) {
- const authorId = AttributeMap.fromString(op.attribs, apool).get('author');
- if (authorId) authorIds.add(authorId);
- }
- }
- const idToName = new Map(parent.parent.pad.userList().map((a) => [a.userId, a.name]));
- const myId = parent.parent.clientVars.userId;
- const authors =
- [...authorIds].map((id) => id === myId ? 'me' : idToName.get(id) || 'unknown');
-
- parent.parent.$.gritter.add({
- title: 'Line Authors',
- text:
- authors.length === 0 ? 'No author information is available'
- : authors.length === 1 ? `The author of this line is ${authors[0]}`
- : `The authors of this line are ${authors.join(' & ')}`,
- sticky: false,
- time: '4000',
- });
- }
- if (!specialHandled && isTypeForSpecialKey &&
- keyCode === 8 &&
- padShortcutEnabled.delete) {
- // "delete" key; in mozilla, if we're at the beginning of a line, normalize now,
- // or else deleting a blank line can take two delete presses.
- // --
- // we do deletes completely customly now:
- // - allows consistent (and better) meta-delete behavior
- // - normalizing and then allowing default behavior confused IE
- // - probably eliminates a few minor quirks
- fastIncorp(3);
- evt.preventDefault();
- doDeleteKey(evt);
- specialHandled = true;
- }
- if (!specialHandled && isTypeForSpecialKey &&
- keyCode === 13 &&
- padShortcutEnabled.return) {
- // return key, handle specially;
- // note that in mozilla we need to do an incorporation for proper return behavior anyway.
- fastIncorp(4);
- evt.preventDefault();
- doReturnKey();
- scheduler.setTimeout(() => {
- outerWin.scrollBy(-100, 0);
- }, 0);
- specialHandled = true;
- }
- if (!specialHandled && isTypeForSpecialKey &&
- keyCode === 27 &&
- padShortcutEnabled.esc) {
- // prevent esc key;
- // in mozilla versions 14-19 avoid reconnecting pad.
-
- fastIncorp(4);
- evt.preventDefault();
- specialHandled = true;
-
- // close all gritters when the user hits escape key
- parent.parent.$.gritter.removeAll();
- }
- if (!specialHandled && isTypeForCmdKey &&
- /* Do a saved revision on ctrl S */
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 's' &&
- !evt.altKey &&
- padShortcutEnabled.cmdS) {
- evt.preventDefault();
- const originalBackground = parent.parent.$('#revisionlink').css('background');
- parent.parent.$('#revisionlink').css({background: 'lightyellow'});
- scheduler.setTimeout(() => {
- parent.parent.$('#revisionlink').css({background: originalBackground});
- }, 1000);
- /* The parent.parent part of this is BAD and I feel bad.. It may break something */
- parent.parent.pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
- specialHandled = true;
- }
- if (!specialHandled && isTypeForSpecialKey &&
- // tab
- keyCode === 9 &&
- !(evt.metaKey || evt.ctrlKey) &&
- padShortcutEnabled.tab) {
- fastIncorp(5);
- evt.preventDefault();
- doTabKey(evt.shiftKey);
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-Z (undo)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'z' &&
- !evt.altKey &&
- padShortcutEnabled.cmdZ) {
- fastIncorp(6);
- evt.preventDefault();
- if (evt.shiftKey) {
- doUndoRedo('redo');
- } else {
- doUndoRedo('undo');
- }
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-Y (redo)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'y' &&
- padShortcutEnabled.cmdY) {
- fastIncorp(10);
- evt.preventDefault();
- doUndoRedo('redo');
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-B (bold)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'b' &&
- padShortcutEnabled.cmdB) {
- fastIncorp(13);
- evt.preventDefault();
- toggleAttributeOnSelection('bold');
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-I (italic)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'i' &&
- padShortcutEnabled.cmdI) {
- fastIncorp(14);
- evt.preventDefault();
- toggleAttributeOnSelection('italic');
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-U (underline)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'u' &&
- padShortcutEnabled.cmdU) {
- fastIncorp(15);
- evt.preventDefault();
- toggleAttributeOnSelection('underline');
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-5 (strikethrough)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === '5' &&
- evt.altKey !== true &&
- padShortcutEnabled.cmd5) {
- fastIncorp(13);
- evt.preventDefault();
- toggleAttributeOnSelection('strikethrough');
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-shift-L (unorderedlist)
- (evt.metaKey || evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'l' &&
- evt.shiftKey &&
- padShortcutEnabled.cmdShiftL) {
- fastIncorp(9);
- evt.preventDefault();
- doInsertUnorderedList();
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-shift-N and cmd-shift-1 (orderedlist)
- (evt.metaKey || evt.ctrlKey) && evt.shiftKey &&
- ((String.fromCharCode(which).toLowerCase() === 'n' && padShortcutEnabled.cmdShiftN) ||
- (String.fromCharCode(which) === '1' && padShortcutEnabled.cmdShift1))) {
- fastIncorp(9);
- evt.preventDefault();
- doInsertOrderedList();
- specialHandled = true;
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-shift-C (clearauthorship)
- (evt.metaKey || evt.ctrlKey) && evt.shiftKey &&
- String.fromCharCode(which).toLowerCase() === 'c' &&
- padShortcutEnabled.cmdShiftC) {
- fastIncorp(9);
- evt.preventDefault();
- CMDS.clearauthorship();
- }
- if (!specialHandled && isTypeForCmdKey &&
- // cmd-H (backspace)
- (evt.ctrlKey) && String.fromCharCode(which).toLowerCase() === 'h' &&
- padShortcutEnabled.cmdH) {
- fastIncorp(20);
- evt.preventDefault();
- doDeleteKey();
- specialHandled = true;
- }
- if (evt.ctrlKey === true && evt.which === 36 &&
- // Control Home send to Y = 0
- padShortcutEnabled.ctrlHome) {
- scroll.setScrollY(0);
- }
- if ((evt.which === 33 || evt.which === 34) && type === 'keydown' && !evt.ctrlKey) {
- // This is required, browsers will try to do normal default behavior on
- // page up / down and the default behavior SUCKS
- evt.preventDefault();
- const oldVisibleLineRange = scroll.getVisibleLineRange(rep);
- let topOffset = rep.selStart[0] - oldVisibleLineRange[0];
- if (topOffset < 0) {
- topOffset = 0;
- }
-
- const isPageDown = evt.which === 34;
- const isPageUp = evt.which === 33;
-
- scheduler.setTimeout(() => {
- // the visible lines IE 1,10
- const newVisibleLineRange = scroll.getVisibleLineRange(rep);
- // total count of lines in pad IE 10
- const linesCount = rep.lines.length();
- // How many lines are in the viewport right now?
- const numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0];
-
- if (isPageUp && padShortcutEnabled.pageUp) {
- // move to the bottom line +1 in the viewport (essentially skipping over a page)
- rep.selEnd[0] -= numberOfLinesInViewport;
- // move to the bottom line +1 in the viewport (essentially skipping over a page)
- rep.selStart[0] -= numberOfLinesInViewport;
- }
-
- // if we hit page down
- if (isPageDown && padShortcutEnabled.pageDown) {
- // If the new viewpoint position is actually further than where we are right now
- if (rep.selEnd[0] >= oldVisibleLineRange[0]) {
- // dont go further in the page down than what's visible IE go from 0 to 50
- // if 50 is visible on screen but dont go below that else we miss content
- rep.selStart[0] = oldVisibleLineRange[1] - 1;
- // dont go further in the page down than what's visible IE go from 0 to 50
- // if 50 is visible on screen but dont go below that else we miss content
- rep.selEnd[0] = oldVisibleLineRange[1] - 1;
- }
- }
-
- // ensure min and max
- if (rep.selEnd[0] < 0) {
- rep.selEnd[0] = 0;
- }
- if (rep.selStart[0] < 0) {
- rep.selStart[0] = 0;
- }
- if (rep.selEnd[0] >= linesCount) {
- rep.selEnd[0] = linesCount - 1;
- }
- updateBrowserSelectionFromRep();
- // get the current caret selection, can't use rep. here because that only gives
- // us the start position not the current
- const myselection = document.getSelection();
- // get the carets selection offset in px IE 214
- let caretOffsetTop = myselection.focusNode.parentNode.offsetTop ||
- myselection.focusNode.offsetTop;
-
- // sometimes the first selection is -1 which causes problems
- // (Especially with ep_page_view)
- // so use focusNode.offsetTop value.
- if (caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop;
- // set the scrollY offset of the viewport on the document
- scroll.setScrollY(caretOffsetTop);
- }, 200);
- }
- }
-
- if (type === 'keydown') {
- idleWorkTimer.atLeast(500);
- } else if (type === 'keypress') {
- // OPINION ASKED. What's going on here? :D
- if (!specialHandled) {
- idleWorkTimer.atMost(0);
- } else {
- idleWorkTimer.atLeast(500);
- }
- } else if (type === 'keyup') {
- const wait = 0;
- idleWorkTimer.atLeast(wait);
- idleWorkTimer.atMost(wait);
- }
-
- // Is part of multi-keystroke international character on Firefox Mac
- const isFirefoxHalfCharacter =
- (browser.firefox && evt.altKey && charCode === 0 && keyCode === 0);
-
- // Is part of multi-keystroke international character on Safari Mac
- const isSafariHalfCharacter =
- (browser.safari && evt.altKey && keyCode === 229);
-
- if (thisKeyDoesntTriggerNormalize || isFirefoxHalfCharacter || isSafariHalfCharacter) {
- idleWorkTimer.atLeast(3000); // give user time to type
- // if this is a keydown, e.g., the keyup shouldn't trigger a normalize
- thisKeyDoesntTriggerNormalize = true;
- }
-
- if (!specialHandled && !thisKeyDoesntTriggerNormalize && !inInternationalComposition &&
- type !== 'keyup') {
- observeChangesAroundSelection();
- }
-
- if (type === 'keyup') {
- thisKeyDoesntTriggerNormalize = false;
- }
- });
- };
-
- let thisKeyDoesntTriggerNormalize = false;
-
- const doUndoRedo = (which) => {
- // precond: normalized DOM
- if (undoModule.enabled) {
- let whichMethod;
- if (which === 'undo') whichMethod = 'performUndo';
- if (which === 'redo') whichMethod = 'performRedo';
- if (whichMethod) {
- const oldEventType = currentCallStack.editEvent.eventType;
- currentCallStack.startNewEvent(which);
- undoModule[whichMethod]((backset, selectionInfo) => {
- if (backset) {
- performDocumentApplyChangeset(backset);
- }
- if (selectionInfo) {
- performSelectionChange(
- lineAndColumnFromChar(selectionInfo.selStart),
- lineAndColumnFromChar(selectionInfo.selEnd),
- selectionInfo.selFocusAtStart);
- }
- const oldEvent = currentCallStack.startNewEvent(oldEventType, true);
- return oldEvent;
- });
- }
- }
- };
- editorInfo.ace_doUndoRedo = doUndoRedo;
-
- const setSelection = (selection) => {
- const copyPoint = (pt) => ({
- node: pt.node,
- index: pt.index,
- maxIndex: pt.maxIndex,
- });
- let isCollapsed;
-
- const pointToRangeBound = (pt) => {
- const p = copyPoint(pt);
- // Make sure Firefox cursor is deep enough; fixes cursor jumping when at top level,
- // and also problem where cut/copy of a whole line selected with fake arrow-keys
- // copies the next line too.
- if (isCollapsed) {
- const diveDeep = () => {
- while (p.node.childNodes.length > 0) {
- if (p.index === 0) {
- p.node = p.node.firstChild;
- p.maxIndex = nodeMaxIndex(p.node);
- } else if (p.index === p.maxIndex) {
- p.node = p.node.lastChild;
- p.maxIndex = nodeMaxIndex(p.node);
- p.index = p.maxIndex;
- } else { break; }
- }
- };
- // now fix problem where cursor at end of text node at end of span-like element
- // with background doesn't seem to show up...
- if (isNodeText(p.node) && p.index === p.maxIndex) {
- let n = p.node;
- while (!n.nextSibling && n !== document.body && n.parentNode !== document.body) {
- n = n.parentNode;
- }
- if (n.nextSibling &&
- !(typeof n.nextSibling.tagName === 'string' &&
- n.nextSibling.tagName.toLowerCase() === 'br') &&
- n !== p.node && n !== document.body && n.parentNode !== document.body) {
- // found a parent, go to next node and dive in
- p.node = n.nextSibling;
- p.maxIndex = nodeMaxIndex(p.node);
- p.index = 0;
- diveDeep();
- }
- }
- // try to make sure insertion point is styled;
- // also fixes other FF problems
- if (!isNodeText(p.node)) {
- diveDeep();
- }
- }
- if (isNodeText(p.node)) {
- return {
- container: p.node,
- offset: p.index,
- };
- } else {
- // p.index in {0,1}
- return {
- container: p.node.parentNode,
- offset: childIndex(p.node) + p.index,
- };
- }
- };
- const browserSelection = window.getSelection();
- if (browserSelection) {
- browserSelection.removeAllRanges();
- if (selection) {
- isCollapsed = (selection.startPoint.node === selection.endPoint.node &&
- selection.startPoint.index === selection.endPoint.index);
- const start = pointToRangeBound(selection.startPoint);
- const end = pointToRangeBound(selection.endPoint);
-
- if (!isCollapsed && selection.focusAtStart &&
- browserSelection.collapse && browserSelection.extend) {
- // can handle "backwards"-oriented selection, shift-arrow-keys move start
- // of selection
- browserSelection.collapse(end.container, end.offset);
- browserSelection.extend(start.container, start.offset);
- } else {
- const range = document.createRange();
- range.setStart(start.container, start.offset);
- range.setEnd(end.container, end.offset);
- browserSelection.removeAllRanges();
- browserSelection.addRange(range);
- }
- }
- }
- };
-
- const updateBrowserSelectionFromRep = () => {
- // requires normalized DOM!
- const selStart = rep.selStart;
- const selEnd = rep.selEnd;
-
- if (!(selStart && selEnd)) {
- setSelection(null);
- return;
- }
-
- const selection = {};
-
- const ss = [selStart[0], selStart[1]];
- selection.startPoint = getPointForLineAndChar(ss);
-
- const se = [selEnd[0], selEnd[1]];
- selection.endPoint = getPointForLineAndChar(se);
-
- selection.focusAtStart = !!rep.selFocusAtStart;
- setSelection(selection);
- };
- editorInfo.ace_updateBrowserSelectionFromRep = updateBrowserSelectionFromRep;
- editorInfo.ace_focus = focus;
- editorInfo.ace_importText = importText;
- editorInfo.ace_importAText = importAText;
- editorInfo.ace_exportText = exportText;
- editorInfo.ace_editorChangedSize = editorChangedSize;
- editorInfo.ace_setOnKeyPress = setOnKeyPress;
- editorInfo.ace_setOnKeyDown = setOnKeyDown;
- editorInfo.ace_setNotifyDirty = setNotifyDirty;
- editorInfo.ace_dispose = dispose;
- editorInfo.ace_setEditable = setEditable;
- editorInfo.ace_execCommand = execCommand;
- editorInfo.ace_replaceRange = replaceRange;
- editorInfo.ace_getAuthorInfos = getAuthorInfos;
- editorInfo.ace_performDocumentReplaceRange = performDocumentReplaceRange;
- editorInfo.ace_performDocumentReplaceCharRange = performDocumentReplaceCharRange;
- editorInfo.ace_setSelection = setSelection;
-
- const nodeMaxIndex = (nd) => {
- if (isNodeText(nd)) return nd.nodeValue.length;
- else return 1;
- };
-
- const getSelection = () => {
- // returns null, or a structure containing startPoint and endPoint,
- // each of which has node (a magicdom node), index, and maxIndex. If the node
- // is a text node, maxIndex is the length of the text; else maxIndex is 1.
- // index is between 0 and maxIndex, inclusive.
- const browserSelection = window.getSelection();
- if (!browserSelection || browserSelection.type === 'None' ||
- browserSelection.rangeCount === 0) {
- return null;
- }
- const range = browserSelection.getRangeAt(0);
-
- const isInBody = (n) => {
- while (n && !(n.tagName && n.tagName.toLowerCase() === 'body')) {
- n = n.parentNode;
- }
- return !!n;
- };
-
- const pointFromRangeBound = (container, offset) => {
- if (!isInBody(container)) {
- // command-click in Firefox selects whole document, HEAD and BODY!
- return {
- node: document.body,
- index: 0,
- maxIndex: 1,
- };
- }
- const n = container;
- const childCount = n.childNodes.length;
- if (isNodeText(n)) {
- return {
- node: n,
- index: offset,
- maxIndex: n.nodeValue.length,
- };
- } else if (childCount === 0) {
- return {
- node: n,
- index: 0,
- maxIndex: 1,
- };
- // treat point between two nodes as BEFORE the second (rather than after the first)
- // if possible; this way point at end of a line block-element is treated as
- // at beginning of next line
- } else if (offset === childCount) {
- const nd = n.childNodes.item(childCount - 1);
- const max = nodeMaxIndex(nd);
- return {
- node: nd,
- index: max,
- maxIndex: max,
- };
- } else {
- const nd = n.childNodes.item(offset);
- const max = nodeMaxIndex(nd);
- return {
- node: nd,
- index: 0,
- maxIndex: max,
- };
- }
- };
- const selection = {
- startPoint: pointFromRangeBound(range.startContainer, range.startOffset),
- endPoint: pointFromRangeBound(range.endContainer, range.endOffset),
- focusAtStart:
- (range.startContainer !== range.endContainer || range.startOffset !== range.endOffset) &&
- browserSelection.anchorNode &&
- browserSelection.anchorNode === range.endContainer &&
- browserSelection.anchorOffset === range.endOffset,
- };
-
- if (selection.startPoint.node.ownerDocument !== window.document) {
- return null;
- }
-
- return selection;
- };
-
- const childIndex = (n) => {
- let idx = 0;
- while (n.previousSibling) {
- idx++;
- n = n.previousSibling;
- }
- return idx;
- };
-
- const fixView = () => {
- // calling this method repeatedly should be fast
- if (getInnerWidth() === 0 || getInnerHeight() === 0) {
- return;
- }
-
- enforceEditability();
-
- $(sideDiv).addClass('sidedivdelayed');
- };
-
- const _teardownActions = [];
-
- const teardown = () => { for (const a of _teardownActions) a(); };
-
- let inInternationalComposition = null;
- editorInfo.ace_getInInternationalComposition = () => inInternationalComposition;
-
- const bindTheEventHandlers = () => {
- $(document).on('keydown', handleKeyEvent);
- $(document).on('keypress', handleKeyEvent);
- $(document).on('keyup', handleKeyEvent);
- $(document).on('click', handleClick);
- // dropdowns on edit bar need to be closed on clicks on both pad inner and pad outer
- $(outerDoc).on('click', hideEditBarDropdowns);
-
- // If non-nullish, pasting on a link should be suppressed.
- let suppressPasteOnLink = null;
-
- $(document.body).on('auxclick', (e) => {
- if (e.originalEvent.button === 1 && (e.target.a || e.target.localName === 'a')) {
- // The user middle-clicked on a link. Usually users do this to open a link in a new tab, but
- // in X11 (Linux) this will instead paste the contents of the primary selection at the mouse
- // cursor. Users almost certainly do not want to paste when middle-clicking on a link, so
- // tell the 'paste' event handler to suppress the paste. This is done by starting a
- // short-lived timer that suppresses paste (when the target is a link) until either the
- // paste event arrives or the timer fires.
- //
- // Why it is implemented this way:
- // * Users want to be able to paste on a link via Ctrl-V, the Edit menu, or the context
- // menu (https://github.com/ether/etherpad-lite/issues/2775) so we cannot simply
- // suppress all paste actions when the target is a link.
- // * Non-X11 systems do not paste when the user middle-clicks, so the paste suppression
- // must be self-resetting.
- // * On non-X11 systems, middle click should continue to open the link in a new tab.
- // Suppressing the middle click here in the 'auxclick' handler (via e.preventDefault())
- // would break that behavior.
- suppressPasteOnLink = scheduler.setTimeout(() => { suppressPasteOnLink = null; }, 0);
- }
- });
-
- $(document.body).on('paste', (e) => {
- if (suppressPasteOnLink != null && (e.target.a || e.target.localName === 'a')) {
- scheduler.clearTimeout(suppressPasteOnLink);
- suppressPasteOnLink = null;
- e.preventDefault();
- return;
- }
-
- // Call paste hook
- hooks.callAll('acePaste', {
- editorInfo,
- rep,
- documentAttributeManager,
- e,
- });
- });
-
- // We reference document here, this is because if we don't this will expose a bug
- // in Google Chrome. This bug will cause the last character on the last line to
- // not fire an event when dropped into..
- $(document).on('drop', (e) => {
- if (e.target.a || e.target.localName === 'a') {
- e.preventDefault();
- }
-
- // Bug fix: when user drags some content and drop it far from its origin, we
- // need to merge the changes into a single changeset. So mark origin with empty
',
- wantHTML: 'empty
',
- wantText: 'empty\n\n',
- },
- 'indentedListsAreNotBullets': {
- description: 'Indented lists are represented with tabs and without bullets',
- input: '',
- wantHTML: '
',
- wantText: '\tindent\n\tindent\n\n',
- },
- 'lineWithMultipleSpaces': {
- description: 'Multiple spaces should be collapsed',
- input: 'Text with more than one space.
',
- wantHTML: 'Text with more than one space.
',
- wantText: 'Text with more than one space.\n\n',
- },
- 'lineWithMultipleNonBreakingAndNormalSpaces': {
- // XXX the HTML between "than" and "one" looks strange
- description: 'non-breaking space should be preserved, but can be replaced when it',
- input: 'Text with more than one space.
',
- wantHTML: 'Text with more than one space.
',
- wantText: 'Text with more than one space.\n\n',
- },
- 'multiplenbsp': {
- description: 'Multiple non-breaking space should be preserved',
- input: '
',
- wantHTML: '
',
- wantText: ' \n\n',
- },
- 'multipleNonBreakingSpaceBetweenWords': {
- description: 'A normal space is always inserted before a word',
- input: ' word1 word2 word3
',
- wantHTML: ' word1 word2 word3
',
- wantText: ' word1 word2 word3\n\n',
- },
- 'nonBreakingSpacePreceededBySpaceBetweenWords': {
- description: 'A non-breaking space preceded by a normal space',
- input: ' word1 word2 word3
',
- wantHTML: ' word1 word2 word3
',
- wantText: ' word1 word2 word3\n\n',
- },
- 'nonBreakingSpaceFollowededBySpaceBetweenWords': {
- description: 'A non-breaking space followed by a normal space',
- input: ' word1 word2 word3
',
- wantHTML: ' word1 word2 word3
',
- wantText: ' word1 word2 word3\n\n',
- },
- 'spacesAfterNewline': {
- description: 'Collapse spaces that follow a newline',
- input: 'something
something
',
- wantHTML: 'something
something
',
- wantText: 'something\nsomething\n\n',
- },
- 'spacesAfterNewlineP': {
- description: 'Collapse spaces that follow a paragraph',
- input: 'something something
',
- wantHTML: 'something
something
',
- wantText: 'something\n\nsomething\n\n',
- },
- 'spacesAtEndOfLine': {
- description: 'Collapse spaces that preceed/follow a newline',
- input: 'something
something
',
- wantHTML: 'something
something
',
- wantText: 'something\nsomething\n\n',
- },
- 'spacesAtEndOfLineP': {
- description: 'Collapse spaces that preceed/follow a paragraph',
- input: 'something something
',
- wantHTML: 'something
something
',
- wantText: 'something\n\nsomething\n\n',
- },
- 'nonBreakingSpacesAfterNewlines': {
- description: 'Don\'t collapse non-breaking spaces that follow a newline',
- input: 'something
something
',
- wantHTML: 'something
something
',
- wantText: 'something\n something\n\n',
- },
- 'nonBreakingSpacesAfterNewlinesP': {
- description: 'Don\'t collapse non-breaking spaces that follow a paragraph',
- input: 'something something
',
- wantHTML: 'something
something
',
- wantText: 'something\n\n something\n\n',
- },
- 'collapseSpacesInsideElements': {
- description: 'Preserve only one space when multiple are present',
- input: 'Need more space s !
',
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
- 'collapseSpacesAcrossNewlines': {
- description: 'Newlines and multiple spaces across newlines should be collapsed',
- input: `
+ 'malformed': {
+ input: 'wtf',
+ wantHTML: 'wtf
',
+ wantText: 'wtf\n\n',
+ disabled: true,
+ },
+ 'nonelistiteminlist #3620': {
+ input: '',
+ wantHTML: '
',
+ wantText: '\ttest\n\t* FOO\n\n',
+ disabled: true,
+ },
+ 'whitespaceinlist #3620': {
+ input: '',
+ wantHTML: '
',
+ wantText: '\t* FOO\n\n',
+ },
+ 'prefixcorrectlinenumber': {
+ input: '- should be 1
- should be 2
',
+ wantHTML: '- should be 1
- should be 2
',
+ wantText: '\t1. should be 1\n\t2. should be 2\n\n',
+ },
+ 'prefixcorrectlinenumbernested': {
+ input: '- should be 1
- foo
- should be 2
',
+ wantHTML: '- should be 1
- foo
- should be 2
',
+ wantText: '\t1. should be 1\n\t\t1.1. foo\n\t2. should be 2\n\n',
+ },
+ /*
+ "prefixcorrectlinenumber when introduced none list item - currently not supported see #3450": {
+ input: '- should be 1
test- should be 2
',
+ wantHTML: '- should be 1
test- should be 2
',
+ wantText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n',
+ }
+ ,
+ "newlinesshouldntresetlinenumber #2194": {
+ input: '- should be 1
test- should be 2
',
+ wantHTML: '- should be 1
test- should be 2
',
+ wantText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n',
+ }
+ */
+ 'ignoreAnyTagsOutsideBody': {
+ description: 'Content outside body should be ignored',
+ input: 'titleempty
',
+ wantHTML: 'empty
',
+ wantText: 'empty\n\n',
+ },
+ 'indentedListsAreNotBullets': {
+ description: 'Indented lists are represented with tabs and without bullets',
+ input: '',
+ wantHTML: '
',
+ wantText: '\tindent\n\tindent\n\n',
+ },
+ 'lineWithMultipleSpaces': {
+ description: 'Multiple spaces should be collapsed',
+ input: 'Text with more than one space.
',
+ wantHTML: 'Text with more than one space.
',
+ wantText: 'Text with more than one space.\n\n',
+ },
+ 'lineWithMultipleNonBreakingAndNormalSpaces': {
+ // XXX the HTML between "than" and "one" looks strange
+ description: 'non-breaking space should be preserved, but can be replaced when it',
+ input: 'Text with more than one space.
',
+ wantHTML: 'Text with more than one space.
',
+ wantText: 'Text with more than one space.\n\n',
+ },
+ 'multiplenbsp': {
+ description: 'Multiple non-breaking space should be preserved',
+ input: '
',
+ wantHTML: '
',
+ wantText: ' \n\n',
+ },
+ 'multipleNonBreakingSpaceBetweenWords': {
+ description: 'A normal space is always inserted before a word',
+ input: ' word1 word2 word3
',
+ wantHTML: ' word1 word2 word3
',
+ wantText: ' word1 word2 word3\n\n',
+ },
+ 'nonBreakingSpacePreceededBySpaceBetweenWords': {
+ description: 'A non-breaking space preceded by a normal space',
+ input: ' word1 word2 word3
',
+ wantHTML: ' word1 word2 word3
',
+ wantText: ' word1 word2 word3\n\n',
+ },
+ 'nonBreakingSpaceFollowededBySpaceBetweenWords': {
+ description: 'A non-breaking space followed by a normal space',
+ input: ' word1 word2 word3
',
+ wantHTML: ' word1 word2 word3
',
+ wantText: ' word1 word2 word3\n\n',
+ },
+ 'spacesAfterNewline': {
+ description: 'Collapse spaces that follow a newline',
+ input: 'something
something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\nsomething\n\n',
+ },
+ 'spacesAfterNewlineP': {
+ description: 'Collapse spaces that follow a paragraph',
+ input: 'something something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\n\nsomething\n\n',
+ },
+ 'spacesAtEndOfLine': {
+ description: 'Collapse spaces that preceed/follow a newline',
+ input: 'something
something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\nsomething\n\n',
+ },
+ 'spacesAtEndOfLineP': {
+ description: 'Collapse spaces that preceed/follow a paragraph',
+ input: 'something something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\n\nsomething\n\n',
+ },
+ 'nonBreakingSpacesAfterNewlines': {
+ description: 'Don\'t collapse non-breaking spaces that follow a newline',
+ input: 'something
something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\n something\n\n',
+ },
+ 'nonBreakingSpacesAfterNewlinesP': {
+ description: 'Don\'t collapse non-breaking spaces that follow a paragraph',
+ input: 'something something
',
+ wantHTML: 'something
something
',
+ wantText: 'something\n\n something\n\n',
+ },
+ 'collapseSpacesInsideElements': {
+ description: 'Preserve only one space when multiple are present',
+ input: 'Need more space s !
',
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
+ 'collapseSpacesAcrossNewlines': {
+ description: 'Newlines and multiple spaces across newlines should be collapsed',
+ input: `
Need
more
space
s
!
`,
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
- 'multipleNewLinesAtBeginning': {
- description: 'Multiple new lines and paragraphs at the beginning should be preserved',
- input: '
first line
second line
',
- wantHTML: '
first line
second line
',
- wantText: '\n\n\n\nfirst line\n\nsecond line\n\n',
- },
- 'multiLineParagraph': {
- description: 'A paragraph with multiple lines should not loose spaces when lines are combined',
- input: `
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
+ 'multipleNewLinesAtBeginning': {
+ description: 'Multiple new lines and paragraphs at the beginning should be preserved',
+ input: '
first line
second line
',
+ wantHTML: '
first line
second line
',
+ wantText: '\n\n\n\nfirst line\n\nsecond line\n\n',
+ },
+ 'multiLineParagraph': {
+ description: 'A paragraph with multiple lines should not loose spaces when lines are combined',
+ input: `
а б в г ґ д е є ж з и і ї й к л м н о
п р с т у ф х ц ч ш щ ю я ь
`,
- wantHTML: 'а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь
',
- wantText: 'а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь\n\n',
- },
- 'multiLineParagraphWithPre': {
- // XXX why is there before "in"?
- description: 'lines in preformatted text should be kept intact',
- input: `
+ wantHTML: 'а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь
',
+ wantText: 'а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь\n\n',
+ },
+ 'multiLineParagraphWithPre': {
+ // XXX why is there before "in"?
+ description: 'lines in preformatted text should be kept intact',
+ input: `
а б в г ґ д е є ж з и і ї й к л м н о
multiple
lines
@@ -188,97 +184,88 @@ const testImports = {
п р с т у ф х ц ч ш щ ю я
ь
`,
- wantHTML: 'а б в г ґ д е є ж з и і ї й к л м н о
multiple
lines
in
pre
п р с т у ф х ц ч ш щ ю я ь
',
- wantText: 'а б в г ґ д е є ж з и і ї й к л м н о\nmultiple\n lines\n in\n pre\n\nп р с т у ф х ц ч ш щ ю я ь\n\n',
- },
- 'preIntroducesASpace': {
- description: 'pre should be on a new line not preceded by a space',
- input: `
+ wantHTML: '
а б в г ґ д е є ж з и і ї й к л м н о
multiple
lines
in
pre
п р с т у ф х ц ч ш щ ю я ь
',
+ wantText: 'а б в г ґ д е є ж з и і ї й к л м н о\nmultiple\n lines\n in\n pre\n\nп р с т у ф х ц ч ш щ ю я ь\n\n',
+ },
+ 'preIntroducesASpace': {
+ description: 'pre should be on a new line not preceded by a space',
+ input: `
1
preline
`,
- wantHTML: '1
preline
',
- wantText: '1\npreline\n\n\n',
- },
- 'dontDeleteSpaceInsideElements': {
- description: 'Preserve spaces inside elements',
- input: 'Need more space s !
',
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
- 'dontDeleteSpaceOutsideElements': {
- description: 'Preserve spaces outside elements',
- input: 'Need more space s !
',
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
- 'dontDeleteSpaceAtEndOfElement': {
- description: 'Preserve spaces at the end of an element',
- input: 'Need more space s !
',
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
- 'dontDeleteSpaceAtBeginOfElements': {
- description: 'Preserve spaces at the start of an element',
- input: 'Need more space s !
',
- wantHTML: 'Need more space s !
',
- wantText: 'Need more space s !\n\n',
- },
+ wantHTML: '1
preline
',
+ wantText: '1\npreline\n\n\n',
+ },
+ 'dontDeleteSpaceInsideElements': {
+ description: 'Preserve spaces inside elements',
+ input: 'Need more space s !
',
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
+ 'dontDeleteSpaceOutsideElements': {
+ description: 'Preserve spaces outside elements',
+ input: 'Need more space s !
',
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
+ 'dontDeleteSpaceAtEndOfElement': {
+ description: 'Preserve spaces at the end of an element',
+ input: 'Need more space s !
',
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
+ 'dontDeleteSpaceAtBeginOfElements': {
+ description: 'Preserve spaces at the start of an element',
+ input: 'Need more space s !
',
+ wantHTML: 'Need more space s !
',
+ wantText: 'Need more space s !\n\n',
+ },
};
-
describe(__filename, function () {
- this.timeout(1000);
-
- before(async function () { agent = await common.init(); });
-
- Object.keys(testImports).forEach((testName) => {
- describe(testName, function () {
- const testPadId = makeid();
- const test = testImports[testName];
- if (test.disabled) {
- return xit(`DISABLED: ${testName}`, function (done) {
- done();
+ this.timeout(1000);
+ before(async function () { agent = await common.init(); });
+ Object.keys(testImports).forEach((testName) => {
+ describe(testName, function () {
+ const testPadId = makeid();
+ const test = testImports[testName];
+ if (test.disabled) {
+ return xit(`DISABLED: ${testName}`, function (done) {
+ done();
+ });
+ }
+ it('createPad', async function () {
+ const res = await agent.get(`${endPoint('createPad')}&padID=${testPadId}`)
+ .expect(200)
+ .expect('Content-Type', /json/);
+ assert.equal(res.body.code, 0);
+ });
+ it('setHTML', async function () {
+ const res = await agent.get(`${endPoint('setHTML')}&padID=${testPadId}` +
+ `&html=${encodeURIComponent(test.input)}`)
+ .expect(200)
+ .expect('Content-Type', /json/);
+ assert.equal(res.body.code, 0);
+ });
+ it('getHTML', async function () {
+ const res = await agent.get(`${endPoint('getHTML')}&padID=${testPadId}`)
+ .expect(200)
+ .expect('Content-Type', /json/);
+ assert.equal(res.body.data.html, test.wantHTML);
+ });
+ it('getText', async function () {
+ const res = await agent.get(`${endPoint('getText')}&padID=${testPadId}`)
+ .expect(200)
+ .expect('Content-Type', /json/);
+ assert.equal(res.body.data.text, test.wantText);
+ });
});
- }
-
- it('createPad', async function () {
- const res = await agent.get(`${endPoint('createPad')}&padID=${testPadId}`)
- .expect(200)
- .expect('Content-Type', /json/);
- assert.equal(res.body.code, 0);
- });
-
- it('setHTML', async function () {
- const res = await agent.get(`${endPoint('setHTML')}&padID=${testPadId}` +
- `&html=${encodeURIComponent(test.input)}`)
- .expect(200)
- .expect('Content-Type', /json/);
- assert.equal(res.body.code, 0);
- });
-
- it('getHTML', async function () {
- const res = await agent.get(`${endPoint('getHTML')}&padID=${testPadId}`)
- .expect(200)
- .expect('Content-Type', /json/);
- assert.equal(res.body.data.html, test.wantHTML);
- });
-
- it('getText', async function () {
- const res = await agent.get(`${endPoint('getText')}&padID=${testPadId}`)
- .expect(200)
- .expect('Content-Type', /json/);
- assert.equal(res.body.data.text, test.wantText);
- });
});
- });
});
-
function makeid() {
- let text = '';
- const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-
- for (let i = 0; i < 5; i++) {
- text += possible.charAt(Math.floor(Math.random() * possible.length));
- }
- return text;
+ let text = '';
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ for (let i = 0; i < 5; i++) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
}
diff --git a/src/tests/backend/specs/api/test.doc b/src/tests/backend/specs/api/test.doc
index c92d46f2e74..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/api/test.doc and b/src/tests/backend/specs/api/test.doc differ
diff --git a/src/tests/backend/specs/api/test.docx b/src/tests/backend/specs/api/test.docx
index 422bb4f037e..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/api/test.docx and b/src/tests/backend/specs/api/test.docx differ
diff --git a/src/tests/backend/specs/api/test.odt b/src/tests/backend/specs/api/test.odt
index 8ab62a61271..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/api/test.odt and b/src/tests/backend/specs/api/test.odt differ
diff --git a/src/tests/backend/specs/api/test.pdf b/src/tests/backend/specs/api/test.pdf
index 774c2ea70c5..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/api/test.pdf and b/src/tests/backend/specs/api/test.pdf differ
diff --git a/src/tests/backend/specs/caching_middleware.js b/src/tests/backend/specs/caching_middleware.js
index ebfd65d9ff0..ca2377d9540 100644
--- a/src/tests/backend/specs/caching_middleware.js
+++ b/src/tests/backend/specs/caching_middleware.js
@@ -1,18 +1,10 @@
+import * as common from "../common.js";
+import assertLegacy from "../assert-legacy.js";
+import queryString from "querystring";
+import * as settings from "../../../node/utils/Settings.js";
'use strict';
-
-/**
- * caching_middleware is responsible for serving everything under path `/javascripts/`
- * That includes packages as defined in `src/node/utils/tar.json` and probably also plugin code
- *
- */
-
-const common = require('../common');
-const assert = require('../assert-legacy').strict;
-const queryString = require('querystring');
-const settings = require('../../../node/utils/Settings');
-
+const assert = assertLegacy.strict;
let agent;
-
/**
* Hack! Returns true if the resource is not plaintext
* The file should start with the callback method, so we need the
@@ -23,95 +15,87 @@ let agent;
* @returns {boolean} if it is plaintext
*/
const isPlaintextResponse = (fileContent, resource) => {
- // callback=require.define&v=1234
- const query = (new URL(resource, 'http://localhost')).search.slice(1);
- // require.define
- const jsonp = queryString.parse(query).callback;
-
- // returns true if the first letters in fileContent equal the content of `jsonp`
- return fileContent.substring(0, jsonp.length) === jsonp;
+ // callback=require.define&v=1234
+ const query = (new URL(resource, 'http://localhost')).search.slice(1);
+ // require.define
+ const jsonp = queryString.parse(query).callback;
+ // returns true if the first letters in fileContent equal the content of `jsonp`
+ return fileContent.substring(0, jsonp.length) === jsonp;
};
-
/**
* A hack to disable `superagent`'s auto unzip functionality
*
* @param {Request} request
*/
const disableAutoDeflate = (request) => {
- request._shouldUnzip = () => false;
+ request._shouldUnzip = () => false;
};
-
describe(__filename, function () {
- const backups = {};
- const fantasyEncoding = 'brainwaves'; // non-working encoding until https://github.com/visionmedia/superagent/pull/1560 is resolved
- const packages = [
- '/javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define',
- '/javascripts/lib/ep_etherpad-lite/static/js/ace2_inner.js?callback=require.define',
- '/javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define',
- '/javascripts/lib/ep_etherpad-lite/static/js/timeslider.js?callback=require.define',
- ];
-
- before(async function () {
- agent = await common.init();
- backups.settings = {};
- backups.settings.minify = settings.minify;
- });
- after(async function () {
- Object.assign(settings, backups.settings);
- });
-
- for (const minify of [false, true]) {
- context(`when minify is ${minify}`, function () {
- before(async function () {
- settings.minify = minify;
- });
-
- describe('gets packages uncompressed without Accept-Encoding gzip', function () {
- for (const resource of packages) {
- it(resource, async function () {
- await agent.get(resource)
- .set('Accept-Encoding', fantasyEncoding)
- .use(disableAutoDeflate)
- .expect(200)
- .expect('Content-Type', /application\/javascript/)
- .expect((res) => {
- assert.equal(res.header['content-encoding'], undefined);
- assert(isPlaintextResponse(res.text, resource));
- });
- });
- }
- });
-
- describe('gets packages compressed with Accept-Encoding gzip', function () {
- for (const resource of packages) {
- it(resource, async function () {
- await agent.get(resource)
- .set('Accept-Encoding', 'gzip')
- .use(disableAutoDeflate)
- .expect(200)
- .expect('Content-Type', /application\/javascript/)
- .expect('Content-Encoding', 'gzip')
- .expect((res) => {
- assert(!isPlaintextResponse(res.text, resource));
- });
- });
- }
- });
-
- it('does not cache content-encoding headers', async function () {
- await agent.get(packages[0])
- .set('Accept-Encoding', fantasyEncoding)
- .expect(200)
- .expect((res) => assert.equal(res.header['content-encoding'], undefined));
- await agent.get(packages[0])
- .set('Accept-Encoding', 'gzip')
- .expect(200)
- .expect('Content-Encoding', 'gzip');
- await agent.get(packages[0])
- .set('Accept-Encoding', fantasyEncoding)
- .expect(200)
- .expect((res) => assert.equal(res.header['content-encoding'], undefined));
- });
+ const backups = {};
+ const fantasyEncoding = 'brainwaves'; // non-working encoding until https://github.com/visionmedia/superagent/pull/1560 is resolved
+ const packages = [
+ '/javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define',
+ '/javascripts/lib/ep_etherpad-lite/static/js/ace2_inner.js?callback=require.define',
+ '/javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define',
+ '/javascripts/lib/ep_etherpad-lite/static/js/timeslider.js?callback=require.define',
+ ];
+ before(async function () {
+ agent = await common.init();
+ backups.settings = {};
+ backups.settings.minify = settings.minify;
+ });
+ after(async function () {
+ Object.assign(settings, backups.settings);
});
- }
+ for (const minify of [false, true]) {
+ context(`when minify is ${minify}`, function () {
+ before(async function () {
+ settings.minify = minify;
+ });
+ describe('gets packages uncompressed without Accept-Encoding gzip', function () {
+ for (const resource of packages) {
+ it(resource, async function () {
+ await agent.get(resource)
+ .set('Accept-Encoding', fantasyEncoding)
+ .use(disableAutoDeflate)
+ .expect(200)
+ .expect('Content-Type', /application\/javascript/)
+ .expect((res) => {
+ assert.equal(res.header['content-encoding'], undefined);
+ assert(isPlaintextResponse(res.text, resource));
+ });
+ });
+ }
+ });
+ describe('gets packages compressed with Accept-Encoding gzip', function () {
+ for (const resource of packages) {
+ it(resource, async function () {
+ await agent.get(resource)
+ .set('Accept-Encoding', 'gzip')
+ .use(disableAutoDeflate)
+ .expect(200)
+ .expect('Content-Type', /application\/javascript/)
+ .expect('Content-Encoding', 'gzip')
+ .expect((res) => {
+ assert(!isPlaintextResponse(res.text, resource));
+ });
+ });
+ }
+ });
+ it('does not cache content-encoding headers', async function () {
+ await agent.get(packages[0])
+ .set('Accept-Encoding', fantasyEncoding)
+ .expect(200)
+ .expect((res) => assert.equal(res.header['content-encoding'], undefined));
+ await agent.get(packages[0])
+ .set('Accept-Encoding', 'gzip')
+ .expect(200)
+ .expect('Content-Encoding', 'gzip');
+ await agent.get(packages[0])
+ .set('Accept-Encoding', fantasyEncoding)
+ .expect(200)
+ .expect((res) => assert.equal(res.header['content-encoding'], undefined));
+ });
+ });
+ }
});
diff --git a/src/tests/backend/specs/chat.js b/src/tests/backend/specs/chat.js
index aefa64183f6..1bf0c1114cb 100644
--- a/src/tests/backend/specs/chat.js
+++ b/src/tests/backend/specs/chat.js
@@ -1,160 +1,153 @@
+import ChatMessage from "../../../static/js/ChatMessage.js";
+import { Pad } from "../../../node/db/Pad.js";
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as padManager from "../../../node/db/PadManager.js";
+import * as pluginDefs from "../../../static/js/pluginfw/plugin_defs.js";
'use strict';
-
-const ChatMessage = require('../../../static/js/ChatMessage');
-const {Pad} = require('../../../node/db/Pad');
-const assert = require('assert').strict;
-const common = require('../common');
-const padManager = require('../../../node/db/PadManager');
-const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
-
+const assert = assert$0.strict;
const logger = common.logger;
-
const checkHook = async (hookName, checkFn) => {
- if (pluginDefs.hooks[hookName] == null) pluginDefs.hooks[hookName] = [];
- await new Promise((resolve, reject) => {
- pluginDefs.hooks[hookName].push({
- hook_fn: async (hookName, context) => {
- if (checkFn == null) return;
- logger.debug(`hook ${hookName} invoked`);
- try {
- // Make sure checkFn is called only once.
- const _checkFn = checkFn;
- checkFn = null;
- await _checkFn(context);
- } catch (err) {
- reject(err);
- return;
- }
- resolve();
- },
+ if (pluginDefs.hooks[hookName] == null)
+ pluginDefs.hooks[hookName] = [];
+ await new Promise((resolve, reject) => {
+ pluginDefs.hooks[hookName].push({
+ hook_fn: async (hookName, context) => {
+ if (checkFn == null)
+ return;
+ logger.debug(`hook ${hookName} invoked`);
+ try {
+ // Make sure checkFn is called only once.
+ const _checkFn = checkFn;
+ checkFn = null;
+ await _checkFn(context);
+ }
+ catch (err) {
+ reject(err);
+ return;
+ }
+ resolve();
+ },
+ });
});
- });
};
-
const sendMessage = (socket, data) => {
- socket.send({
- type: 'COLLABROOM',
- component: 'pad',
- data,
- });
+ socket.send({
+ type: 'COLLABROOM',
+ component: 'pad',
+ data,
+ });
};
-
-const sendChat = (socket, message) => sendMessage(socket, {type: 'CHAT_MESSAGE', message});
-
+const sendChat = (socket, message) => sendMessage(socket, { type: 'CHAT_MESSAGE', message });
describe(__filename, function () {
- const padId = 'testChatPad';
- const hooksBackup = {};
-
- before(async function () {
- for (const [name, defs] of Object.entries(pluginDefs.hooks)) {
- if (defs == null) continue;
- hooksBackup[name] = defs;
- }
- });
-
- beforeEach(async function () {
- for (const [name, defs] of Object.entries(hooksBackup)) pluginDefs.hooks[name] = [...defs];
- for (const name of Object.keys(pluginDefs.hooks)) {
- if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
- }
- if (await padManager.doesPadExist(padId)) {
- const pad = await padManager.getPad(padId);
- await pad.remove();
- }
- });
-
- after(async function () {
- Object.assign(pluginDefs.hooks, hooksBackup);
- for (const name of Object.keys(pluginDefs.hooks)) {
- if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
- }
- });
-
- describe('chatNewMessage hook', function () {
- let authorId;
- let socket;
-
- beforeEach(async function () {
- socket = await common.connect();
- const {data: clientVars} = await common.handshake(socket, padId);
- authorId = clientVars.userId;
- });
-
- afterEach(async function () {
- socket.close();
- });
-
- it('message', async function () {
- const start = Date.now();
- await Promise.all([
- checkHook('chatNewMessage', ({message}) => {
- assert(message != null);
- assert(message instanceof ChatMessage);
- assert.equal(message.authorId, authorId);
- assert.equal(message.text, this.test.title);
- assert(message.time >= start);
- assert(message.time <= Date.now());
- }),
- sendChat(socket, {text: this.test.title}),
- ]);
+ const padId = 'testChatPad';
+ const hooksBackup = {};
+ before(async function () {
+ for (const [name, defs] of Object.entries(pluginDefs.hooks)) {
+ if (defs == null)
+ continue;
+ hooksBackup[name] = defs;
+ }
});
-
- it('pad', async function () {
- await Promise.all([
- checkHook('chatNewMessage', ({pad}) => {
- assert(pad != null);
- assert(pad instanceof Pad);
- assert.equal(pad.id, padId);
- }),
- sendChat(socket, {text: this.test.title}),
- ]);
+ beforeEach(async function () {
+ for (const [name, defs] of Object.entries(hooksBackup))
+ pluginDefs.hooks[name] = [...defs];
+ for (const name of Object.keys(pluginDefs.hooks)) {
+ if (hooksBackup[name] == null)
+ delete pluginDefs.hooks[name];
+ }
+ if (await padManager.doesPadExist(padId)) {
+ const pad = await padManager.getPad(padId);
+ await pad.remove();
+ }
});
-
- it('padId', async function () {
- await Promise.all([
- checkHook('chatNewMessage', (context) => {
- assert.equal(context.padId, padId);
- }),
- sendChat(socket, {text: this.test.title}),
- ]);
+ after(async function () {
+ Object.assign(pluginDefs.hooks, hooksBackup);
+ for (const name of Object.keys(pluginDefs.hooks)) {
+ if (hooksBackup[name] == null)
+ delete pluginDefs.hooks[name];
+ }
});
-
- it('mutations propagate', async function () {
- const listen = async (type) => await new Promise((resolve) => {
- const handler = (msg) => {
- if (msg.type !== 'COLLABROOM') return;
- if (msg.data == null || msg.data.type !== type) return;
- resolve(msg.data);
- socket.off('message', handler);
- };
- socket.on('message', handler);
- });
-
- const modifiedText = `${this.test.title} `;
- const customMetadata = {foo: this.test.title};
- await Promise.all([
- checkHook('chatNewMessage', ({message}) => {
- message.text = modifiedText;
- message.customMetadata = customMetadata;
- }),
- (async () => {
- const {message} = await listen('CHAT_MESSAGE');
- assert(message != null);
- assert.equal(message.text, modifiedText);
- assert.deepEqual(message.customMetadata, customMetadata);
- })(),
- sendChat(socket, {text: this.test.title}),
- ]);
- // Simulate fetch of historical chat messages when a pad is first loaded.
- await Promise.all([
- (async () => {
- const {messages: [message]} = await listen('CHAT_MESSAGES');
- assert(message != null);
- assert.equal(message.text, modifiedText);
- assert.deepEqual(message.customMetadata, customMetadata);
- })(),
- sendMessage(socket, {type: 'GET_CHAT_MESSAGES', start: 0, end: 0}),
- ]);
+ describe('chatNewMessage hook', function () {
+ let authorId;
+ let socket;
+ beforeEach(async function () {
+ socket = await common.connect();
+ const { data: clientVars } = await common.handshake(socket, padId);
+ authorId = clientVars.userId;
+ });
+ afterEach(async function () {
+ socket.close();
+ });
+ it('message', async function () {
+ const start = Date.now();
+ await Promise.all([
+ checkHook('chatNewMessage', ({ message }) => {
+ assert(message != null);
+ assert(message instanceof ChatMessage);
+ assert.equal(message.authorId, authorId);
+ assert.equal(message.text, this.test.title);
+ assert(message.time >= start);
+ assert(message.time <= Date.now());
+ }),
+ sendChat(socket, { text: this.test.title }),
+ ]);
+ });
+ it('pad', async function () {
+ await Promise.all([
+ checkHook('chatNewMessage', ({ pad }) => {
+ assert(pad != null);
+ assert(pad instanceof Pad);
+ assert.equal(pad.id, padId);
+ }),
+ sendChat(socket, { text: this.test.title }),
+ ]);
+ });
+ it('padId', async function () {
+ await Promise.all([
+ checkHook('chatNewMessage', (context) => {
+ assert.equal(context.padId, padId);
+ }),
+ sendChat(socket, { text: this.test.title }),
+ ]);
+ });
+ it('mutations propagate', async function () {
+ const listen = async (type) => await new Promise((resolve) => {
+ const handler = (msg) => {
+ if (msg.type !== 'COLLABROOM')
+ return;
+ if (msg.data == null || msg.data.type !== type)
+ return;
+ resolve(msg.data);
+ socket.off('message', handler);
+ };
+ socket.on('message', handler);
+ });
+ const modifiedText = `${this.test.title} `;
+ const customMetadata = { foo: this.test.title };
+ await Promise.all([
+ checkHook('chatNewMessage', ({ message }) => {
+ message.text = modifiedText;
+ message.customMetadata = customMetadata;
+ }),
+ (async () => {
+ const { message } = await listen('CHAT_MESSAGE');
+ assert(message != null);
+ assert.equal(message.text, modifiedText);
+ assert.deepEqual(message.customMetadata, customMetadata);
+ })(),
+ sendChat(socket, { text: this.test.title }),
+ ]);
+ // Simulate fetch of historical chat messages when a pad is first loaded.
+ await Promise.all([
+ (async () => {
+ const { messages: [message] } = await listen('CHAT_MESSAGES');
+ assert(message != null);
+ assert.equal(message.text, modifiedText);
+ assert.deepEqual(message.customMetadata, customMetadata);
+ })(),
+ sendMessage(socket, { type: 'GET_CHAT_MESSAGES', start: 0, end: 0 }),
+ ]);
+ });
});
- });
});
diff --git a/src/tests/backend/specs/contentcollector.js b/src/tests/backend/specs/contentcollector.js
index a4696307edf..5e4ad92d946 100644
--- a/src/tests/backend/specs/contentcollector.js
+++ b/src/tests/backend/specs/contentcollector.js
@@ -1,284 +1,273 @@
+import AttributePool from "../../../static/js/AttributePool.js";
+import * as Changeset from "../../../static/js/Changeset.js";
+import assert$0 from "assert";
+import * as attributes from "../../../static/js/attributes.js";
+import * as contentcollector from "../../../static/js/contentcollector.js";
+import * as jsdom from "jsdom";
'use strict';
-
-/*
- * While importexport tests target the `setHTML` API endpoint, which is nearly identical to what
- * happens when a user manually imports a document via the UI, the contentcollector tests here don't
- * use rehype to process the document. Rehype removes spaces and newĺines were applicable, so the
- * expected results here can differ from importexport.js.
- *
- * If you add tests here, please also add them to importexport.js
- */
-
-const AttributePool = require('../../../static/js/AttributePool');
-const Changeset = require('../../../static/js/Changeset');
-const assert = require('assert').strict;
-const attributes = require('../../../static/js/attributes');
-const contentcollector = require('../../../static/js/contentcollector');
-const jsdom = require('jsdom');
-
+const assert = assert$0.strict;
// All test case `wantAlines` values must only refer to attributes in this list so that the
// attribute numbers do not change due to changes in pool insertion order.
const knownAttribs = [
- ['insertorder', 'first'],
- ['italic', 'true'],
- ['list', 'bullet1'],
- ['list', 'bullet2'],
- ['list', 'number1'],
- ['list', 'number2'],
- ['lmkr', '1'],
- ['start', '1'],
- ['start', '2'],
+ ['insertorder', 'first'],
+ ['italic', 'true'],
+ ['list', 'bullet1'],
+ ['list', 'bullet2'],
+ ['list', 'number1'],
+ ['list', 'number2'],
+ ['lmkr', '1'],
+ ['start', '1'],
+ ['start', '2'],
];
-
const testCases = [
- {
- description: 'Simple',
- html: 'foo
',
- wantAlines: ['+3'],
- wantText: ['foo'],
- },
- {
- description: 'Line starts with asterisk',
- html: '*foo
',
- wantAlines: ['+4'],
- wantText: ['*foo'],
- },
- {
- description: 'Complex nested Li',
- html: '- one
- 1.1
- two
',
- wantAlines: [
- '*0*4*6*7+1+3',
- '*0*5*6*8+1+3',
- '*0*4*6*8+1+3',
- ],
- wantText: [
- '*one', '*1.1', '*two',
- ],
- },
- {
- description: 'Complex list of different types',
- html: '- item
- item1
- item2
',
- wantAlines: [
- '*0*2*6+1+3',
- '*0*2*6+1+3',
- '*0*2*6+1+1',
- '*0*2*6+1+1',
- '*0*2*6+1+1',
- '*0*3*6+1+1',
- '*0*3*6+1+1',
- '*0*4*6*7+1+4',
- '*0*5*6*8+1+5',
- '*0*5*6*8+1+5',
- ],
- wantText: [
- '*one',
- '*two',
- '*0',
- '*1',
- '*2',
- '*3',
- '*4',
- '*item',
- '*item1',
- '*item2',
- ],
- },
- {
- description: 'Tests if uls properly get attributes',
- html: 'div
foo
',
- wantAlines: [
- '*0*2*6+1+1',
- '*0*2*6+1+1',
- '+3',
- '+3',
- ],
- wantText: ['*a', '*b', 'div', 'foo'],
- },
- {
- description: 'Tests if indented uls properly get attributes',
- html: 'foo
',
- wantAlines: [
- '*0*2*6+1+1',
- '*0*3*6+1+1',
- '*0*2*6+1+1',
- '+3',
- ],
- wantText: ['*a', '*b', '*a', 'foo'],
- },
- {
- description: 'Tests if ols properly get line numbers when in a normal OL',
- html: '- a
- b
- c
test
',
- wantAlines: [
- '*0*4*6*7+1+1',
- '*0*4*6*7+1+1',
- '*0*4*6*7+1+1',
- '+4',
- ],
- wantText: ['*a', '*b', '*c', 'test'],
- noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
- },
- {
- description: 'A single completely empty line break within an ol should reset count if OL is closed off..',
- html: '- should be 1
hello
- should be 1
- should be 2
',
- wantAlines: [
- '*0*4*6*7+1+b',
- '+5',
- '*0*4*6*8+1+b',
- '*0*4*6*8+1+b',
- '',
- ],
- wantText: ['*should be 1', 'hello', '*should be 1', '*should be 2', ''],
- noteToSelf: "Shouldn't include attribute marker in the line",
- },
- {
- description: 'A single
should create a new line',
- html: '',
- wantAlines: ['', ''],
- wantText: ['', ''],
- noteToSelf: 'should create a line break but not break numbering',
- },
- {
- description: 'Tests if ols properly get line numbers when in a normal OL #2',
- html: 'a- b
- c
notlistfoo
',
- wantAlines: [
- '+1',
- '*0*4*6*7+1+1',
- '*0*5*6*8+1+1',
- '+7',
- '+3',
- ],
- wantText: ['a', '*b', '*c', 'notlist', 'foo'],
- noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
- },
- {
- description: 'First item being an UL then subsequent being OL will fail',
- html: '',
- wantAlines: ['+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1'],
- wantText: ['a', '*b', '*c'],
- noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
- disabled: true,
- },
- {
- description: 'A single completely empty line break within an ol should NOT reset count',
- html: '- should be 1
- should be 2
- should be 3
',
- wantAlines: [],
- wantText: ['*should be 1', '*should be 2', '*should be 3'],
- noteToSelf: "should create a line break but not break numbering -- This is what I can't get working!",
- disabled: true,
- },
- {
- description: 'Content outside body should be ignored',
- html: 'titleempty
',
- wantAlines: ['+5'],
- wantText: ['empty'],
- },
- {
- description: 'Multiple spaces should be preserved',
- html: 'Text with more than one space.
',
- wantAlines: ['+10'],
- wantText: ['Text with more than one space.'],
- },
- {
- description: 'non-breaking and normal space should be preserved',
- html: 'Text with more than one space.
',
- wantAlines: ['+10'],
- wantText: ['Text with more than one space.'],
- },
- {
- description: 'Multiple nbsp should be preserved',
- html: '
',
- wantAlines: ['+2'],
- wantText: [' '],
- },
- {
- description: 'Multiple nbsp between words ',
- html: ' word1 word2 word3
',
- wantAlines: ['+m'],
- wantText: [' word1 word2 word3'],
- },
- {
- description: 'A non-breaking space preceded by a normal space',
- html: ' word1 word2 word3
',
- wantAlines: ['+l'],
- wantText: [' word1 word2 word3'],
- },
- {
- description: 'A non-breaking space followed by a normal space',
- html: ' word1 word2 word3
',
- wantAlines: ['+l'],
- wantText: [' word1 word2 word3'],
- },
- {
- description: 'Don\'t collapse spaces that follow a newline',
- html: 'something
something
',
- wantAlines: ['+9', '+m'],
- wantText: ['something', ' something'],
- },
- {
- description: 'Don\'t collapse spaces that follow a empty paragraph',
- html: 'something something
',
- wantAlines: ['+9', '', '+m'],
- wantText: ['something', '', ' something'],
- },
- {
- description: 'Don\'t collapse spaces that preceed/follow a newline',
- html: 'something
something
',
- wantAlines: ['+l', '+m'],
- wantText: ['something ', ' something'],
- },
- {
- description: 'Don\'t collapse spaces that preceed/follow a empty paragraph',
- html: 'something something
',
- wantAlines: ['+l', '', '+m'],
- wantText: ['something ', '', ' something'],
- },
- {
- description: 'Don\'t collapse non-breaking spaces that follow a newline',
- html: 'something
something
',
- wantAlines: ['+9', '+c'],
- wantText: ['something', ' something'],
- },
- {
- description: 'Don\'t collapse non-breaking spaces that follow a paragraph',
- html: 'something something
',
- wantAlines: ['+9', '', '+c'],
- wantText: ['something', '', ' something'],
- },
- {
- description: 'Preserve all spaces when multiple are present',
- html: 'Need more space s !
',
- wantAlines: ['+h*1+4+2'],
- wantText: ['Need more space s !'],
- },
- {
- description: 'Newlines and multiple spaces across newlines should be preserved',
- html: `
+ {
+ description: 'Simple',
+ html: 'foo
',
+ wantAlines: ['+3'],
+ wantText: ['foo'],
+ },
+ {
+ description: 'Line starts with asterisk',
+ html: '*foo
',
+ wantAlines: ['+4'],
+ wantText: ['*foo'],
+ },
+ {
+ description: 'Complex nested Li',
+ html: '- one
- 1.1
- two
',
+ wantAlines: [
+ '*0*4*6*7+1+3',
+ '*0*5*6*8+1+3',
+ '*0*4*6*8+1+3',
+ ],
+ wantText: [
+ '*one', '*1.1', '*two',
+ ],
+ },
+ {
+ description: 'Complex list of different types',
+ html: '- item
- item1
- item2
',
+ wantAlines: [
+ '*0*2*6+1+3',
+ '*0*2*6+1+3',
+ '*0*2*6+1+1',
+ '*0*2*6+1+1',
+ '*0*2*6+1+1',
+ '*0*3*6+1+1',
+ '*0*3*6+1+1',
+ '*0*4*6*7+1+4',
+ '*0*5*6*8+1+5',
+ '*0*5*6*8+1+5',
+ ],
+ wantText: [
+ '*one',
+ '*two',
+ '*0',
+ '*1',
+ '*2',
+ '*3',
+ '*4',
+ '*item',
+ '*item1',
+ '*item2',
+ ],
+ },
+ {
+ description: 'Tests if uls properly get attributes',
+ html: 'div
foo
',
+ wantAlines: [
+ '*0*2*6+1+1',
+ '*0*2*6+1+1',
+ '+3',
+ '+3',
+ ],
+ wantText: ['*a', '*b', 'div', 'foo'],
+ },
+ {
+ description: 'Tests if indented uls properly get attributes',
+ html: 'foo
',
+ wantAlines: [
+ '*0*2*6+1+1',
+ '*0*3*6+1+1',
+ '*0*2*6+1+1',
+ '+3',
+ ],
+ wantText: ['*a', '*b', '*a', 'foo'],
+ },
+ {
+ description: 'Tests if ols properly get line numbers when in a normal OL',
+ html: '- a
- b
- c
test
',
+ wantAlines: [
+ '*0*4*6*7+1+1',
+ '*0*4*6*7+1+1',
+ '*0*4*6*7+1+1',
+ '+4',
+ ],
+ wantText: ['*a', '*b', '*c', 'test'],
+ noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
+ },
+ {
+ description: 'A single completely empty line break within an ol should reset count if OL is closed off..',
+ html: '- should be 1
hello
- should be 1
- should be 2
',
+ wantAlines: [
+ '*0*4*6*7+1+b',
+ '+5',
+ '*0*4*6*8+1+b',
+ '*0*4*6*8+1+b',
+ '',
+ ],
+ wantText: ['*should be 1', 'hello', '*should be 1', '*should be 2', ''],
+ noteToSelf: "Shouldn't include attribute marker in the line",
+ },
+ {
+ description: 'A single
should create a new line',
+ html: '',
+ wantAlines: ['', ''],
+ wantText: ['', ''],
+ noteToSelf: 'should create a line break but not break numbering',
+ },
+ {
+ description: 'Tests if ols properly get line numbers when in a normal OL #2',
+ html: 'a- b
- c
notlistfoo
',
+ wantAlines: [
+ '+1',
+ '*0*4*6*7+1+1',
+ '*0*5*6*8+1+1',
+ '+7',
+ '+3',
+ ],
+ wantText: ['a', '*b', '*c', 'notlist', 'foo'],
+ noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
+ },
+ {
+ description: 'First item being an UL then subsequent being OL will fail',
+ html: '',
+ wantAlines: ['+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1'],
+ wantText: ['a', '*b', '*c'],
+ noteToSelf: 'Ensure empty P does not induce line attribute marker, wont this break the editor?',
+ disabled: true,
+ },
+ {
+ description: 'A single completely empty line break within an ol should NOT reset count',
+ html: '- should be 1
- should be 2
- should be 3
',
+ wantAlines: [],
+ wantText: ['*should be 1', '*should be 2', '*should be 3'],
+ noteToSelf: "should create a line break but not break numbering -- This is what I can't get working!",
+ disabled: true,
+ },
+ {
+ description: 'Content outside body should be ignored',
+ html: 'titleempty
',
+ wantAlines: ['+5'],
+ wantText: ['empty'],
+ },
+ {
+ description: 'Multiple spaces should be preserved',
+ html: 'Text with more than one space.
',
+ wantAlines: ['+10'],
+ wantText: ['Text with more than one space.'],
+ },
+ {
+ description: 'non-breaking and normal space should be preserved',
+ html: 'Text with more than one space.
',
+ wantAlines: ['+10'],
+ wantText: ['Text with more than one space.'],
+ },
+ {
+ description: 'Multiple nbsp should be preserved',
+ html: '
',
+ wantAlines: ['+2'],
+ wantText: [' '],
+ },
+ {
+ description: 'Multiple nbsp between words ',
+ html: ' word1 word2 word3
',
+ wantAlines: ['+m'],
+ wantText: [' word1 word2 word3'],
+ },
+ {
+ description: 'A non-breaking space preceded by a normal space',
+ html: ' word1 word2 word3
',
+ wantAlines: ['+l'],
+ wantText: [' word1 word2 word3'],
+ },
+ {
+ description: 'A non-breaking space followed by a normal space',
+ html: ' word1 word2 word3
',
+ wantAlines: ['+l'],
+ wantText: [' word1 word2 word3'],
+ },
+ {
+ description: 'Don\'t collapse spaces that follow a newline',
+ html: 'something
something
',
+ wantAlines: ['+9', '+m'],
+ wantText: ['something', ' something'],
+ },
+ {
+ description: 'Don\'t collapse spaces that follow a empty paragraph',
+ html: 'something something
',
+ wantAlines: ['+9', '', '+m'],
+ wantText: ['something', '', ' something'],
+ },
+ {
+ description: 'Don\'t collapse spaces that preceed/follow a newline',
+ html: 'something
something
',
+ wantAlines: ['+l', '+m'],
+ wantText: ['something ', ' something'],
+ },
+ {
+ description: 'Don\'t collapse spaces that preceed/follow a empty paragraph',
+ html: 'something something
',
+ wantAlines: ['+l', '', '+m'],
+ wantText: ['something ', '', ' something'],
+ },
+ {
+ description: 'Don\'t collapse non-breaking spaces that follow a newline',
+ html: 'something
something
',
+ wantAlines: ['+9', '+c'],
+ wantText: ['something', ' something'],
+ },
+ {
+ description: 'Don\'t collapse non-breaking spaces that follow a paragraph',
+ html: 'something something
',
+ wantAlines: ['+9', '', '+c'],
+ wantText: ['something', '', ' something'],
+ },
+ {
+ description: 'Preserve all spaces when multiple are present',
+ html: 'Need more space s !
',
+ wantAlines: ['+h*1+4+2'],
+ wantText: ['Need more space s !'],
+ },
+ {
+ description: 'Newlines and multiple spaces across newlines should be preserved',
+ html: `
Need
more
space
s
!
`,
- wantAlines: ['+19*1+4+b'],
- wantText: ['Need more space s !'],
- },
- {
- description: 'Multiple new lines at the beginning should be preserved',
- html: '
first line
second line
',
- wantAlines: ['', '', '', '', '+a', '', '+b'],
- wantText: ['', '', '', '', 'first line', '', 'second line'],
- },
- {
- description: 'A paragraph with multiple lines should not loose spaces when lines are combined',
- html: `
+ wantAlines: ['+19*1+4+b'],
+ wantText: ['Need more space s !'],
+ },
+ {
+ description: 'Multiple new lines at the beginning should be preserved',
+ html: '
first line
second line
',
+ wantAlines: ['', '', '', '', '+a', '', '+b'],
+ wantText: ['', '', '', '', 'first line', '', 'second line'],
+ },
+ {
+ description: 'A paragraph with multiple lines should not loose spaces when lines are combined',
+ html: `
а б в г ґ д е є ж з и і ї й к л м н о
п р с т у ф х ц ч ш щ ю я ь
`,
- wantAlines: ['+1t'],
- wantText: ['а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь'],
- },
- {
- description: 'lines in preformatted text should be kept intact',
- html: `
+ wantAlines: ['+1t'],
+ wantText: ['а б в г ґ д е є ж з и і ї й к л м н о п р с т у ф х ц ч ш щ ю я ь'],
+ },
+ {
+ description: 'lines in preformatted text should be kept intact',
+ html: `
а б в г ґ д е є ж з и і ї й к л м н о
multiple
lines
in
@@ -286,101 +275,98 @@ pre
п р с т у ф х ц ч ш щ ю я
ь
`,
- wantAlines: ['+11', '+8', '+5', '+2', '+3', '+r'],
- wantText: [
- 'а б в г ґ д е є ж з и і ї й к л м н о',
- 'multiple',
- 'lines',
- 'in',
- 'pre',
- 'п р с т у ф х ц ч ш щ ю я ь',
- ],
- },
- {
- description: 'pre should be on a new line not preceded by a space',
- html: `
+ wantAlines: ['+11', '+8', '+5', '+2', '+3', '+r'],
+ wantText: [
+ 'а б в г ґ д е є ж з и і ї й к л м н о',
+ 'multiple',
+ 'lines',
+ 'in',
+ 'pre',
+ 'п р с т у ф х ц ч ш щ ю я ь',
+ ],
+ },
+ {
+ description: 'pre should be on a new line not preceded by a space',
+ html: `
1
preline
`,
- wantAlines: ['+6', '+7'],
- wantText: [' 1 ', 'preline'],
- },
- {
- description: 'Preserve spaces on the beginning and end of a element',
- html: 'Need more space s !
',
- wantAlines: ['+f*1+3+1'],
- wantText: ['Need more space s !'],
- },
- {
- description: 'Preserve spaces outside elements',
- html: 'Need more space s !
',
- wantAlines: ['+g*1+1+2'],
- wantText: ['Need more space s !'],
- },
- {
- description: 'Preserve spaces at the end of an element',
- html: 'Need more space s !
',
- wantAlines: ['+g*1+2+1'],
- wantText: ['Need more space s !'],
- },
- {
- description: 'Preserve spaces at the start of an element',
- html: 'Need more space s !
',
- wantAlines: ['+f*1+2+2'],
- wantText: ['Need more space s !'],
- },
+ wantAlines: ['+6', '+7'],
+ wantText: [' 1 ', 'preline'],
+ },
+ {
+ description: 'Preserve spaces on the beginning and end of a element',
+ html: 'Need more space s !
',
+ wantAlines: ['+f*1+3+1'],
+ wantText: ['Need more space s !'],
+ },
+ {
+ description: 'Preserve spaces outside elements',
+ html: 'Need more space s !
',
+ wantAlines: ['+g*1+1+2'],
+ wantText: ['Need more space s !'],
+ },
+ {
+ description: 'Preserve spaces at the end of an element',
+ html: 'Need more space s !
',
+ wantAlines: ['+g*1+2+1'],
+ wantText: ['Need more space s !'],
+ },
+ {
+ description: 'Preserve spaces at the start of an element',
+ html: 'Need more space s !
',
+ wantAlines: ['+f*1+2+2'],
+ wantText: ['Need more space s !'],
+ },
];
-
describe(__filename, function () {
- for (const tc of testCases) {
- describe(tc.description, function () {
- let apool;
- let result;
-
- before(async function () {
- if (tc.disabled) return this.skip();
- const {window: {document}} = new jsdom.JSDOM(tc.html);
- apool = new AttributePool();
- // To reduce test fragility, the attribute pool is seeded with `knownAttribs`, and all
- // attributes in `tc.wantAlines` must be in `knownAttribs`. (This guarantees that attribute
- // numbers do not change if the attribute processing code changes.)
- for (const attrib of knownAttribs) apool.putAttrib(attrib);
- for (const aline of tc.wantAlines) {
- for (const op of Changeset.deserializeOps(aline)) {
- for (const n of attributes.decodeAttribString(op.attribs)) {
- assert(n < knownAttribs.length);
- }
- }
- }
- const cc = contentcollector.makeContentCollector(true, null, apool);
- cc.collectContent(document.body);
- result = cc.finish();
- });
-
- it('text matches', async function () {
- assert.deepEqual(result.lines, tc.wantText);
- });
-
- it('alines match', async function () {
- assert.deepEqual(result.lineAttribs, tc.wantAlines);
- });
-
- it('attributes are sorted in canonical order', async function () {
- const gotAttribs = [];
- const wantAttribs = [];
- for (const aline of result.lineAttribs) {
- const gotAlineAttribs = [];
- gotAttribs.push(gotAlineAttribs);
- const wantAlineAttribs = [];
- wantAttribs.push(wantAlineAttribs);
- for (const op of Changeset.deserializeOps(aline)) {
- const gotOpAttribs = [...attributes.attribsFromString(op.attribs, apool)];
- gotAlineAttribs.push(gotOpAttribs);
- wantAlineAttribs.push(attributes.sort([...gotOpAttribs]));
- }
- }
- assert.deepEqual(gotAttribs, wantAttribs);
- });
- });
- }
+ for (const tc of testCases) {
+ describe(tc.description, function () {
+ let apool;
+ let result;
+ before(async function () {
+ if (tc.disabled)
+ return this.skip();
+ const { window: { document } } = new jsdom.JSDOM(tc.html);
+ apool = new AttributePool();
+ // To reduce test fragility, the attribute pool is seeded with `knownAttribs`, and all
+ // attributes in `tc.wantAlines` must be in `knownAttribs`. (This guarantees that attribute
+ // numbers do not change if the attribute processing code changes.)
+ for (const attrib of knownAttribs)
+ apool.putAttrib(attrib);
+ for (const aline of tc.wantAlines) {
+ for (const op of Changeset.deserializeOps(aline)) {
+ for (const n of attributes.decodeAttribString(op.attribs)) {
+ assert(n < knownAttribs.length);
+ }
+ }
+ }
+ const cc = contentcollector.makeContentCollector(true, null, apool);
+ cc.collectContent(document.body);
+ result = cc.finish();
+ });
+ it('text matches', async function () {
+ assert.deepEqual(result.lines, tc.wantText);
+ });
+ it('alines match', async function () {
+ assert.deepEqual(result.lineAttribs, tc.wantAlines);
+ });
+ it('attributes are sorted in canonical order', async function () {
+ const gotAttribs = [];
+ const wantAttribs = [];
+ for (const aline of result.lineAttribs) {
+ const gotAlineAttribs = [];
+ gotAttribs.push(gotAlineAttribs);
+ const wantAlineAttribs = [];
+ wantAttribs.push(wantAlineAttribs);
+ for (const op of Changeset.deserializeOps(aline)) {
+ const gotOpAttribs = [...attributes.attribsFromString(op.attribs, apool)];
+ gotAlineAttribs.push(gotOpAttribs);
+ wantAlineAttribs.push(attributes.sort([...gotOpAttribs]));
+ }
+ }
+ assert.deepEqual(gotAttribs, wantAttribs);
+ });
+ });
+ }
});
diff --git a/src/tests/backend/specs/export.js b/src/tests/backend/specs/export.js
index d2fcde131dc..ea4a50be035 100644
--- a/src/tests/backend/specs/export.js
+++ b/src/tests/backend/specs/export.js
@@ -1,26 +1,21 @@
+import * as common from "../common.js";
+import * as padManager from "../../../node/db/PadManager.js";
+import * as settings from "../../../node/utils/Settings.js";
'use strict';
-
-const common = require('../common');
-const padManager = require('../../../node/db/PadManager');
-const settings = require('../../../node/utils/Settings');
-
describe(__filename, function () {
- let agent;
- const settingsBackup = {};
-
- before(async function () {
- agent = await common.init();
- settingsBackup.soffice = settings.soffice;
- await padManager.getPad('testExportPad', 'test content');
- });
-
- after(async function () {
- Object.assign(settings, settingsBackup);
- });
-
- it('returns 500 on export error', async function () {
- settings.soffice = 'false'; // '/bin/false' doesn't work on Windows
- await agent.get('/p/testExportPad/export/doc')
- .expect(500);
- });
+ let agent;
+ const settingsBackup = {};
+ before(async function () {
+ agent = await common.init();
+ settingsBackup.soffice = settings.soffice;
+ await padManager.getPad('testExportPad', 'test content');
+ });
+ after(async function () {
+ Object.assign(settings, settingsBackup);
+ });
+ it('returns 500 on export error', async function () {
+ settings.soffice = 'false'; // '/bin/false' doesn't work on Windows
+ await agent.get('/p/testExportPad/export/doc')
+ .expect(500);
+ });
});
diff --git a/src/tests/backend/specs/favicon-test-custom.png b/src/tests/backend/specs/favicon-test-custom.png
index 9c6532c9610..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/favicon-test-custom.png and b/src/tests/backend/specs/favicon-test-custom.png differ
diff --git a/src/tests/backend/specs/favicon-test-skin.png b/src/tests/backend/specs/favicon-test-skin.png
index 87bdadbbb32..e69de29bb2d 100644
Binary files a/src/tests/backend/specs/favicon-test-skin.png and b/src/tests/backend/specs/favicon-test-skin.png differ
diff --git a/src/tests/backend/specs/favicon.js b/src/tests/backend/specs/favicon.js
index a5e3095dee8..c69dfb3af82 100644
--- a/src/tests/backend/specs/favicon.js
+++ b/src/tests/backend/specs/favicon.js
@@ -1,91 +1,83 @@
+import assert$0 from "assert";
+import * as common from "../common.js";
+import fs from "fs";
+import path from "path";
+import * as settings from "../../../node/utils/Settings.js";
+import superagent from "superagent";
'use strict';
-
-const assert = require('assert').strict;
-const common = require('../common');
-const fs = require('fs');
+const assert = assert$0.strict;
const fsp = fs.promises;
-const path = require('path');
-const settings = require('../../../node/utils/Settings');
-const superagent = require('superagent');
-
describe(__filename, function () {
- let agent;
- let backupSettings;
- let skinDir;
- let wantCustomIcon;
- let wantDefaultIcon;
- let wantSkinIcon;
-
- before(async function () {
- agent = await common.init();
- wantCustomIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-custom.png'));
- wantDefaultIcon = await fsp.readFile(path.join(settings.root, 'src', 'static', 'favicon.ico'));
- wantSkinIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-skin.png'));
- });
-
- beforeEach(async function () {
- backupSettings = {...settings};
- skinDir = await fsp.mkdtemp(path.join(settings.root, 'src', 'static', 'skins', 'test-'));
- settings.skinName = path.basename(skinDir);
- });
-
- afterEach(async function () {
- delete settings.favicon;
- delete settings.skinName;
- Object.assign(settings, backupSettings);
- try {
- // TODO: The {recursive: true} option wasn't added to fsp.rmdir() until Node.js v12.10.0 so we
- // can't rely on it until support for Node.js v10 is dropped.
- await fsp.unlink(path.join(skinDir, 'favicon.ico'));
- await fsp.rmdir(skinDir, {recursive: true});
- } catch (err) { /* intentionally ignored */ }
- });
-
- it('uses custom favicon if set (relative pathname)', async function () {
- settings.favicon =
- path.relative(settings.root, path.join(__dirname, 'favicon-test-custom.png'));
- assert(!path.isAbsolute(settings.favicon));
- const {body: gotIcon} = await agent.get('/favicon.ico')
- .accept('png').buffer(true).parse(superagent.parse.image)
- .expect(200);
- assert(gotIcon.equals(wantCustomIcon));
- });
-
- it('uses custom favicon if set (absolute pathname)', async function () {
- settings.favicon = path.join(__dirname, 'favicon-test-custom.png');
- assert(path.isAbsolute(settings.favicon));
- const {body: gotIcon} = await agent.get('/favicon.ico')
- .accept('png').buffer(true).parse(superagent.parse.image)
- .expect(200);
- assert(gotIcon.equals(wantCustomIcon));
- });
-
- it('falls back if custom favicon is missing', async function () {
- // The previous default for settings.favicon was 'favicon.ico', so many users will continue to
- // have that in their settings.json for a long time. There is unlikely to be a favicon at
- // path.resolve(settings.root, 'favicon.ico'), so this test ensures that 'favicon.ico' won't be
- // a problem for those users.
- settings.favicon = 'favicon.ico';
- const {body: gotIcon} = await agent.get('/favicon.ico')
- .accept('png').buffer(true).parse(superagent.parse.image)
- .expect(200);
- assert(gotIcon.equals(wantDefaultIcon));
- });
-
- it('uses skin favicon if present', async function () {
- await fsp.writeFile(path.join(skinDir, 'favicon.ico'), wantSkinIcon);
- settings.favicon = null;
- const {body: gotIcon} = await agent.get('/favicon.ico')
- .accept('png').buffer(true).parse(superagent.parse.image)
- .expect(200);
- assert(gotIcon.equals(wantSkinIcon));
- });
-
- it('falls back to default favicon', async function () {
- settings.favicon = null;
- const {body: gotIcon} = await agent.get('/favicon.ico')
- .accept('png').buffer(true).parse(superagent.parse.image)
- .expect(200);
- assert(gotIcon.equals(wantDefaultIcon));
- });
+ let agent;
+ let backupSettings;
+ let skinDir;
+ let wantCustomIcon;
+ let wantDefaultIcon;
+ let wantSkinIcon;
+ before(async function () {
+ agent = await common.init();
+ wantCustomIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-custom.png'));
+ wantDefaultIcon = await fsp.readFile(path.join(settings.root, 'src', 'static', 'favicon.ico'));
+ wantSkinIcon = await fsp.readFile(path.join(__dirname, 'favicon-test-skin.png'));
+ });
+ beforeEach(async function () {
+ backupSettings = { ...settings };
+ skinDir = await fsp.mkdtemp(path.join(settings.root, 'src', 'static', 'skins', 'test-'));
+ settings.skinName = path.basename(skinDir);
+ });
+ afterEach(async function () {
+ delete settings.favicon;
+ delete settings.skinName;
+ Object.assign(settings, backupSettings);
+ try {
+ // TODO: The {recursive: true} option wasn't added to fsp.rmdir() until Node.js v12.10.0 so we
+ // can't rely on it until support for Node.js v10 is dropped.
+ await fsp.unlink(path.join(skinDir, 'favicon.ico'));
+ await fsp.rmdir(skinDir, { recursive: true });
+ }
+ catch (err) { /* intentionally ignored */ }
+ });
+ it('uses custom favicon if set (relative pathname)', async function () {
+ settings.favicon =
+ path.relative(settings.root, path.join(__dirname, 'favicon-test-custom.png'));
+ assert(!path.isAbsolute(settings.favicon));
+ const { body: gotIcon } = await agent.get('/favicon.ico')
+ .accept('png').buffer(true).parse(superagent.parse.image)
+ .expect(200);
+ assert(gotIcon.equals(wantCustomIcon));
+ });
+ it('uses custom favicon if set (absolute pathname)', async function () {
+ settings.favicon = path.join(__dirname, 'favicon-test-custom.png');
+ assert(path.isAbsolute(settings.favicon));
+ const { body: gotIcon } = await agent.get('/favicon.ico')
+ .accept('png').buffer(true).parse(superagent.parse.image)
+ .expect(200);
+ assert(gotIcon.equals(wantCustomIcon));
+ });
+ it('falls back if custom favicon is missing', async function () {
+ // The previous default for settings.favicon was 'favicon.ico', so many users will continue to
+ // have that in their settings.json for a long time. There is unlikely to be a favicon at
+ // path.resolve(settings.root, 'favicon.ico'), so this test ensures that 'favicon.ico' won't be
+ // a problem for those users.
+ settings.favicon = 'favicon.ico';
+ const { body: gotIcon } = await agent.get('/favicon.ico')
+ .accept('png').buffer(true).parse(superagent.parse.image)
+ .expect(200);
+ assert(gotIcon.equals(wantDefaultIcon));
+ });
+ it('uses skin favicon if present', async function () {
+ await fsp.writeFile(path.join(skinDir, 'favicon.ico'), wantSkinIcon);
+ settings.favicon = null;
+ const { body: gotIcon } = await agent.get('/favicon.ico')
+ .accept('png').buffer(true).parse(superagent.parse.image)
+ .expect(200);
+ assert(gotIcon.equals(wantSkinIcon));
+ });
+ it('falls back to default favicon', async function () {
+ settings.favicon = null;
+ const { body: gotIcon } = await agent.get('/favicon.ico')
+ .accept('png').buffer(true).parse(superagent.parse.image)
+ .expect(200);
+ assert(gotIcon.equals(wantDefaultIcon));
+ });
});
diff --git a/src/tests/backend/specs/health.js b/src/tests/backend/specs/health.js
index 0090aedbb90..bc640af239e 100644
--- a/src/tests/backend/specs/health.js
+++ b/src/tests/backend/specs/health.js
@@ -1,56 +1,48 @@
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as settings from "../../../node/utils/Settings.js";
+import superagent from "superagent";
'use strict';
-
-const assert = require('assert').strict;
-const common = require('../common');
-const settings = require('../../../node/utils/Settings');
-const superagent = require('superagent');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- let agent;
- const backup = {};
-
- const getHealth = () => agent.get('/health')
- .accept('application/health+json')
- .buffer(true)
- .parse(superagent.parse['application/json'])
- .expect(200)
- .expect((res) => assert.equal(res.type, 'application/health+json'));
-
- before(async function () {
- agent = await common.init();
- });
-
- beforeEach(async function () {
- backup.settings = {};
- for (const setting of ['requireAuthentication', 'requireAuthorization']) {
- backup.settings[setting] = settings[setting];
- }
- });
-
- afterEach(async function () {
- Object.assign(settings, backup.settings);
- });
-
- it('/health works', async function () {
- const res = await getHealth();
- assert.equal(res.body.status, 'pass');
- assert.equal(res.body.releaseId, settings.getEpVersion());
- });
-
- it('auth is not required', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- const res = await getHealth();
- assert.equal(res.body.status, 'pass');
- });
-
- // We actually want to test that no express-session state is created, but that is difficult to do
- // without intrusive changes or unpleasant ueberdb digging. Instead, we assume that the lack of a
- // cookie means that no express-session state was created (how would express-session look up the
- // session state if no ID was returned to the client?).
- it('no cookie is returned', async function () {
- const res = await getHealth();
- const cookie = res.headers['set-cookie'];
- assert(cookie == null, `unexpected Set-Cookie: ${cookie}`);
- });
+ let agent;
+ const backup = {};
+ const getHealth = () => agent.get('/health')
+ .accept('application/health+json')
+ .buffer(true)
+ .parse(superagent.parse['application/json'])
+ .expect(200)
+ .expect((res) => assert.equal(res.type, 'application/health+json'));
+ before(async function () {
+ agent = await common.init();
+ });
+ beforeEach(async function () {
+ backup.settings = {};
+ for (const setting of ['requireAuthentication', 'requireAuthorization']) {
+ backup.settings[setting] = settings[setting];
+ }
+ });
+ afterEach(async function () {
+ Object.assign(settings, backup.settings);
+ });
+ it('/health works', async function () {
+ const res = await getHealth();
+ assert.equal(res.body.status, 'pass');
+ assert.equal(res.body.releaseId, settings.getEpVersion());
+ });
+ it('auth is not required', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ const res = await getHealth();
+ assert.equal(res.body.status, 'pass');
+ });
+ // We actually want to test that no express-session state is created, but that is difficult to do
+ // without intrusive changes or unpleasant ueberdb digging. Instead, we assume that the lack of a
+ // cookie means that no express-session state was created (how would express-session look up the
+ // session state if no ID was returned to the client?).
+ it('no cookie is returned', async function () {
+ const res = await getHealth();
+ const cookie = res.headers['set-cookie'];
+ assert(cookie == null, `unexpected Set-Cookie: ${cookie}`);
+ });
});
diff --git a/src/tests/backend/specs/hooks.js b/src/tests/backend/specs/hooks.js
index 3120911aea2..e8972943a7a 100644
--- a/src/tests/backend/specs/hooks.js
+++ b/src/tests/backend/specs/hooks.js
@@ -1,1209 +1,1106 @@
+import assertLegacy from "../assert-legacy.js";
+import * as hooks from "../../../static/js/pluginfw/hooks.js";
+import * as plugins from "../../../static/js/pluginfw/plugin_defs.js";
+import sinon from "sinon";
'use strict';
-
-const assert = require('../assert-legacy').strict;
-const hooks = require('../../../static/js/pluginfw/hooks');
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const sinon = require('sinon');
-
+const assert = assertLegacy.strict;
describe(__filename, function () {
- const hookName = 'testHook';
- const hookFnName = 'testPluginFileName:testHookFunctionName';
- let testHooks; // Convenience shorthand for plugins.hooks[hookName].
- let hook; // Convenience shorthand for plugins.hooks[hookName][0].
-
- beforeEach(async function () {
- // Make sure these are not already set so that we don't accidentally step on someone else's
- // toes:
- assert(plugins.hooks[hookName] == null);
- assert(hooks.deprecationNotices[hookName] == null);
- assert(hooks.exportedForTestingOnly.deprecationWarned[hookFnName] == null);
-
- // Many of the tests only need a single registered hook function. Set that up here to reduce
- // boilerplate.
- hook = makeHook();
- plugins.hooks[hookName] = [hook];
- testHooks = plugins.hooks[hookName];
- });
-
- afterEach(async function () {
- sinon.restore();
- delete plugins.hooks[hookName];
- delete hooks.deprecationNotices[hookName];
- delete hooks.exportedForTestingOnly.deprecationWarned[hookFnName];
- });
-
- const makeHook = (ret) => ({
- hook_name: hookName,
- // Many tests will likely want to change this. Unfortunately, we can't use a convenience
- // wrapper like `(...args) => hookFn(..args)` because the hooks look at Function.length and
- // change behavior depending on the number of parameters.
- hook_fn: (hn, ctx, cb) => cb(ret),
- hook_fn_name: hookFnName,
- part: {plugin: 'testPluginName'},
- });
-
- // Hook functions that should work for both synchronous and asynchronous hooks.
- const supportedSyncHookFunctions = [
- {
- name: 'return non-Promise value, with callback parameter',
- fn: (hn, ctx, cb) => 'val',
- want: 'val',
- syncOk: true,
- },
- {
- name: 'return non-Promise value, without callback parameter',
- fn: (hn, ctx) => 'val',
- want: 'val',
- syncOk: true,
- },
- {
- name: 'return undefined, without callback parameter',
- fn: (hn, ctx) => {},
- want: undefined,
- syncOk: true,
- },
- {
- name: 'pass non-Promise value to callback',
- fn: (hn, ctx, cb) => { cb('val'); },
- want: 'val',
- syncOk: true,
- },
- {
- name: 'pass undefined to callback',
- fn: (hn, ctx, cb) => { cb(); },
- want: undefined,
- syncOk: true,
- },
- {
- name: 'return the value returned from the callback',
- fn: (hn, ctx, cb) => cb('val'),
- want: 'val',
- syncOk: true,
- },
- {
- name: 'throw',
- fn: (hn, ctx, cb) => { throw new Error('test exception'); },
- wantErr: 'test exception',
- syncOk: true,
- },
- ];
-
- describe('callHookFnSync', function () {
- const callHookFnSync = hooks.exportedForTestingOnly.callHookFnSync; // Convenience shorthand.
-
- describe('basic behavior', function () {
- it('passes hook name', async function () {
- hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
- callHookFnSync(hook);
- });
-
- it('passes context', async function () {
- for (const val of ['value', null, undefined]) {
- hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
- callHookFnSync(hook, val);
- }
- });
-
- it('returns the value provided to the callback', async function () {
- for (const val of ['value', null, undefined]) {
- hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
- assert.equal(callHookFnSync(hook, val), val);
- }
- });
-
- it('returns the value returned by the hook function', async function () {
- for (const val of ['value', null, undefined]) {
- // Must not have the cb parameter otherwise returning undefined will error.
- hook.hook_fn = (hn, ctx) => ctx;
- assert.equal(callHookFnSync(hook, val), val);
- }
- });
-
- it('does not catch exceptions', async function () {
- hook.hook_fn = () => { throw new Error('test exception'); };
- assert.throws(() => callHookFnSync(hook), {message: 'test exception'});
- });
-
- it('callback returns undefined', async function () {
- hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
- callHookFnSync(hook);
- });
-
- it('checks for deprecation', async function () {
- sinon.stub(console, 'warn');
- hooks.deprecationNotices[hookName] = 'test deprecation';
- callHookFnSync(hook);
- assert.equal(hooks.exportedForTestingOnly.deprecationWarned[hookFnName], true);
- assert.equal(console.warn.callCount, 1);
- assert.match(console.warn.getCall(0).args[0], /test deprecation/);
- });
+ const hookName = 'testHook';
+ const hookFnName = 'testPluginFileName:testHookFunctionName';
+ let testHooks; // Convenience shorthand for plugins.hooks[hookName].
+ let hook; // Convenience shorthand for plugins.hooks[hookName][0].
+ beforeEach(async function () {
+ // Make sure these are not already set so that we don't accidentally step on someone else's
+ // toes:
+ assert(plugins.hooks[hookName] == null);
+ assert(hooks.deprecationNotices[hookName] == null);
+ assert(hooks.exportedForTestingOnly.deprecationWarned[hookFnName] == null);
+ // Many of the tests only need a single registered hook function. Set that up here to reduce
+ // boilerplate.
+ hook = makeHook();
+ plugins.hooks[hookName] = [hook];
+ testHooks = plugins.hooks[hookName];
});
-
- describe('supported hook function styles', function () {
- for (const tc of supportedSyncHookFunctions) {
- it(tc.name, async function () {
- sinon.stub(console, 'warn');
- sinon.stub(console, 'error');
- hook.hook_fn = tc.fn;
- const call = () => callHookFnSync(hook);
- if (tc.wantErr) {
- assert.throws(call, {message: tc.wantErr});
- } else {
- assert.equal(call(), tc.want);
- }
- assert.equal(console.warn.callCount, 0);
- assert.equal(console.error.callCount, 0);
- });
- }
+ afterEach(async function () {
+ sinon.restore();
+ delete plugins.hooks[hookName];
+ delete hooks.deprecationNotices[hookName];
+ delete hooks.exportedForTestingOnly.deprecationWarned[hookFnName];
});
-
- describe('bad hook function behavior (other than double settle)', function () {
- const promise1 = Promise.resolve('val1');
- const promise2 = Promise.resolve('val2');
-
- const testCases = [
+ const makeHook = (ret) => ({
+ hook_name: hookName,
+ // Many tests will likely want to change this. Unfortunately, we can't use a convenience
+ // wrapper like `(...args) => hookFn(..args)` because the hooks look at Function.length and
+ // change behavior depending on the number of parameters.
+ hook_fn: (hn, ctx, cb) => cb(ret),
+ hook_fn_name: hookFnName,
+ part: { plugin: 'testPluginName' },
+ });
+ // Hook functions that should work for both synchronous and asynchronous hooks.
+ const supportedSyncHookFunctions = [
{
- name: 'never settles -> buggy hook detected',
- // Note that returning undefined without calling the callback is permitted if the function
- // has 2 or fewer parameters, so this test function must have 3 parameters.
- fn: (hn, ctx, cb) => {},
- wantVal: undefined,
- wantError: /UNSETTLED FUNCTION BUG/,
+ name: 'return non-Promise value, with callback parameter',
+ fn: (hn, ctx, cb) => 'val',
+ want: 'val',
+ syncOk: true,
},
{
- name: 'returns a Promise -> buggy hook detected',
- fn: () => promise1,
- wantVal: promise1,
- wantError: /PROHIBITED PROMISE BUG/,
+ name: 'return non-Promise value, without callback parameter',
+ fn: (hn, ctx) => 'val',
+ want: 'val',
+ syncOk: true,
},
{
- name: 'passes a Promise to cb -> buggy hook detected',
- fn: (hn, ctx, cb) => cb(promise2),
- wantVal: promise2,
- wantError: /PROHIBITED PROMISE BUG/,
+ name: 'return undefined, without callback parameter',
+ fn: (hn, ctx) => { },
+ want: undefined,
+ syncOk: true,
},
- ];
-
- for (const tc of testCases) {
- it(tc.name, async function () {
- sinon.stub(console, 'error');
- hook.hook_fn = tc.fn;
- assert.equal(callHookFnSync(hook), tc.wantVal);
- assert.equal(console.error.callCount, tc.wantError ? 1 : 0);
- if (tc.wantError) assert.match(console.error.getCall(0).args[0], tc.wantError);
- });
- }
- });
-
- // Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
- // time, or call the callback and then return a value.)
- describe('bad hook function behavior (double settle)', function () {
- beforeEach(async function () {
- sinon.stub(console, 'error');
- });
-
- // Each item in this array codifies a way to settle a synchronous hook function. Each of the
- // test cases below combines two of these behaviors in a single hook function and confirms
- // that callHookFnSync both (1) returns the result of the first settle attempt, and
- // (2) detects the second settle attempt.
- const behaviors = [
{
- name: 'throw',
- fn: (cb, err, val) => { throw err; },
- rejects: true,
+ name: 'pass non-Promise value to callback',
+ fn: (hn, ctx, cb) => { cb('val'); },
+ want: 'val',
+ syncOk: true,
},
{
- name: 'return value',
- fn: (cb, err, val) => val,
+ name: 'pass undefined to callback',
+ fn: (hn, ctx, cb) => { cb(); },
+ want: undefined,
+ syncOk: true,
},
{
- name: 'immediately call cb(value)',
- fn: (cb, err, val) => cb(val),
+ name: 'return the value returned from the callback',
+ fn: (hn, ctx, cb) => cb('val'),
+ want: 'val',
+ syncOk: true,
},
{
- name: 'defer call to cb(value)',
- fn: (cb, err, val) => { process.nextTick(cb, val); },
- async: true,
+ name: 'throw',
+ fn: (hn, ctx, cb) => { throw new Error('test exception'); },
+ wantErr: 'test exception',
+ syncOk: true,
},
- ];
-
- for (const step1 of behaviors) {
- // There can't be a second step if the first step is to return or throw.
- if (step1.name.startsWith('return ') || step1.name === 'throw') continue;
- for (const step2 of behaviors) {
- // If step1 and step2 are both async then there would be three settle attempts (first an
- // erroneous unsettled return, then async step 1, then async step 2). Handling triple
- // settle would complicate the tests, and it is sufficient to test only double settles.
- if (step1.async && step2.async) continue;
-
- it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
- hook.hook_fn = (hn, ctx, cb) => {
- step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
- return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
- };
-
- // Temporarily remove unhandled error listeners so that the errors we expect to see
- // don't trigger a test failure (or terminate node).
- const events = ['uncaughtException', 'unhandledRejection'];
- const listenerBackups = {};
- for (const event of events) {
- listenerBackups[event] = process.rawListeners(event);
- process.removeAllListeners(event);
- }
-
- // We should see an asynchronous error (either an unhandled Promise rejection or an
- // uncaught exception) if and only if one of the two steps was asynchronous or there was
- // a throw (in which case the double settle is deferred so that the caller sees the
- // original error).
- const wantAsyncErr = step1.async || step2.async || step2.rejects;
- let tempListener;
- let asyncErr;
- try {
- const seenErrPromise = new Promise((resolve) => {
- tempListener = (err) => {
- assert.equal(asyncErr, undefined);
- asyncErr = err;
- resolve();
- };
- if (!wantAsyncErr) resolve();
- });
- events.forEach((event) => process.on(event, tempListener));
- const call = () => callHookFnSync(hook, {ret1: 'val1', ret2: 'val2'});
- if (step2.rejects) {
- assert.throws(call, {message: 'val2'});
- } else if (!step1.async && !step2.async) {
- assert.throws(call, {message: /DOUBLE SETTLE BUG/});
- } else {
- assert.equal(call(), step1.async ? 'val2' : 'val1');
- }
- await seenErrPromise;
- } finally {
- // Restore the original listeners.
- for (const event of events) {
- process.off(event, tempListener);
- for (const listener of listenerBackups[event]) {
- process.on(event, listener);
+ ];
+ describe('callHookFnSync', function () {
+ const callHookFnSync = hooks.exportedForTestingOnly.callHookFnSync; // Convenience shorthand.
+ describe('basic behavior', function () {
+ it('passes hook name', async function () {
+ hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
+ callHookFnSync(hook);
+ });
+ it('passes context', async function () {
+ for (const val of ['value', null, undefined]) {
+ hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
+ callHookFnSync(hook, val);
+ }
+ });
+ it('returns the value provided to the callback', async function () {
+ for (const val of ['value', null, undefined]) {
+ hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
+ assert.equal(callHookFnSync(hook, val), val);
}
- }
+ });
+ it('returns the value returned by the hook function', async function () {
+ for (const val of ['value', null, undefined]) {
+ // Must not have the cb parameter otherwise returning undefined will error.
+ hook.hook_fn = (hn, ctx) => ctx;
+ assert.equal(callHookFnSync(hook, val), val);
+ }
+ });
+ it('does not catch exceptions', async function () {
+ hook.hook_fn = () => { throw new Error('test exception'); };
+ assert.throws(() => callHookFnSync(hook), { message: 'test exception' });
+ });
+ it('callback returns undefined', async function () {
+ hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
+ callHookFnSync(hook);
+ });
+ it('checks for deprecation', async function () {
+ sinon.stub(console, 'warn');
+ hooks.deprecationNotices[hookName] = 'test deprecation';
+ callHookFnSync(hook);
+ assert.equal(hooks.exportedForTestingOnly.deprecationWarned[hookFnName], true);
+ assert.equal(console.warn.callCount, 1);
+ assert.match(console.warn.getCall(0).args[0], /test deprecation/);
+ });
+ });
+ describe('supported hook function styles', function () {
+ for (const tc of supportedSyncHookFunctions) {
+ it(tc.name, async function () {
+ sinon.stub(console, 'warn');
+ sinon.stub(console, 'error');
+ hook.hook_fn = tc.fn;
+ const call = () => callHookFnSync(hook);
+ if (tc.wantErr) {
+ assert.throws(call, { message: tc.wantErr });
+ }
+ else {
+ assert.equal(call(), tc.want);
+ }
+ assert.equal(console.warn.callCount, 0);
+ assert.equal(console.error.callCount, 0);
+ });
}
- assert.equal(console.error.callCount, 1);
- assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
- if (wantAsyncErr) {
- assert(asyncErr instanceof Error);
- assert.match(asyncErr.message, /DOUBLE SETTLE BUG/);
+ });
+ describe('bad hook function behavior (other than double settle)', function () {
+ const promise1 = Promise.resolve('val1');
+ const promise2 = Promise.resolve('val2');
+ const testCases = [
+ {
+ name: 'never settles -> buggy hook detected',
+ // Note that returning undefined without calling the callback is permitted if the function
+ // has 2 or fewer parameters, so this test function must have 3 parameters.
+ fn: (hn, ctx, cb) => { },
+ wantVal: undefined,
+ wantError: /UNSETTLED FUNCTION BUG/,
+ },
+ {
+ name: 'returns a Promise -> buggy hook detected',
+ fn: () => promise1,
+ wantVal: promise1,
+ wantError: /PROHIBITED PROMISE BUG/,
+ },
+ {
+ name: 'passes a Promise to cb -> buggy hook detected',
+ fn: (hn, ctx, cb) => cb(promise2),
+ wantVal: promise2,
+ wantError: /PROHIBITED PROMISE BUG/,
+ },
+ ];
+ for (const tc of testCases) {
+ it(tc.name, async function () {
+ sinon.stub(console, 'error');
+ hook.hook_fn = tc.fn;
+ assert.equal(callHookFnSync(hook), tc.wantVal);
+ assert.equal(console.error.callCount, tc.wantError ? 1 : 0);
+ if (tc.wantError)
+ assert.match(console.error.getCall(0).args[0], tc.wantError);
+ });
}
- });
-
- // This next test is the same as the above test, except the second settle attempt is for
- // the same outcome. The two outcomes can't be the same if one step throws and the other
- // doesn't, so skip those cases.
- if (step1.rejects !== step2.rejects) continue;
-
- it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
- const err = new Error('val');
- hook.hook_fn = (hn, ctx, cb) => {
- step1.fn(cb, err, 'val');
- return step2.fn(cb, err, 'val');
- };
-
- const errorLogged = new Promise((resolve) => console.error.callsFake(resolve));
- const call = () => callHookFnSync(hook);
- if (step2.rejects) {
- assert.throws(call, {message: 'val'});
- } else {
- assert.equal(call(), 'val');
+ });
+ // Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
+ // time, or call the callback and then return a value.)
+ describe('bad hook function behavior (double settle)', function () {
+ beforeEach(async function () {
+ sinon.stub(console, 'error');
+ });
+ // Each item in this array codifies a way to settle a synchronous hook function. Each of the
+ // test cases below combines two of these behaviors in a single hook function and confirms
+ // that callHookFnSync both (1) returns the result of the first settle attempt, and
+ // (2) detects the second settle attempt.
+ const behaviors = [
+ {
+ name: 'throw',
+ fn: (cb, err, val) => { throw err; },
+ rejects: true,
+ },
+ {
+ name: 'return value',
+ fn: (cb, err, val) => val,
+ },
+ {
+ name: 'immediately call cb(value)',
+ fn: (cb, err, val) => cb(val),
+ },
+ {
+ name: 'defer call to cb(value)',
+ fn: (cb, err, val) => { process.nextTick(cb, val); },
+ async: true,
+ },
+ ];
+ for (const step1 of behaviors) {
+ // There can't be a second step if the first step is to return or throw.
+ if (step1.name.startsWith('return ') || step1.name === 'throw')
+ continue;
+ for (const step2 of behaviors) {
+ // If step1 and step2 are both async then there would be three settle attempts (first an
+ // erroneous unsettled return, then async step 1, then async step 2). Handling triple
+ // settle would complicate the tests, and it is sufficient to test only double settles.
+ if (step1.async && step2.async)
+ continue;
+ it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
+ hook.hook_fn = (hn, ctx, cb) => {
+ step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
+ return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
+ };
+ // Temporarily remove unhandled error listeners so that the errors we expect to see
+ // don't trigger a test failure (or terminate node).
+ const events = ['uncaughtException', 'unhandledRejection'];
+ const listenerBackups = {};
+ for (const event of events) {
+ listenerBackups[event] = process.rawListeners(event);
+ process.removeAllListeners(event);
+ }
+ // We should see an asynchronous error (either an unhandled Promise rejection or an
+ // uncaught exception) if and only if one of the two steps was asynchronous or there was
+ // a throw (in which case the double settle is deferred so that the caller sees the
+ // original error).
+ const wantAsyncErr = step1.async || step2.async || step2.rejects;
+ let tempListener;
+ let asyncErr;
+ try {
+ const seenErrPromise = new Promise((resolve) => {
+ tempListener = (err) => {
+ assert.equal(asyncErr, undefined);
+ asyncErr = err;
+ resolve();
+ };
+ if (!wantAsyncErr)
+ resolve();
+ });
+ events.forEach((event) => process.on(event, tempListener));
+ const call = () => callHookFnSync(hook, { ret1: 'val1', ret2: 'val2' });
+ if (step2.rejects) {
+ assert.throws(call, { message: 'val2' });
+ }
+ else if (!step1.async && !step2.async) {
+ assert.throws(call, { message: /DOUBLE SETTLE BUG/ });
+ }
+ else {
+ assert.equal(call(), step1.async ? 'val2' : 'val1');
+ }
+ await seenErrPromise;
+ }
+ finally {
+ // Restore the original listeners.
+ for (const event of events) {
+ process.off(event, tempListener);
+ for (const listener of listenerBackups[event]) {
+ process.on(event, listener);
+ }
+ }
+ }
+ assert.equal(console.error.callCount, 1);
+ assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
+ if (wantAsyncErr) {
+ assert(asyncErr instanceof Error);
+ assert.match(asyncErr.message, /DOUBLE SETTLE BUG/);
+ }
+ });
+ // This next test is the same as the above test, except the second settle attempt is for
+ // the same outcome. The two outcomes can't be the same if one step throws and the other
+ // doesn't, so skip those cases.
+ if (step1.rejects !== step2.rejects)
+ continue;
+ it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
+ const err = new Error('val');
+ hook.hook_fn = (hn, ctx, cb) => {
+ step1.fn(cb, err, 'val');
+ return step2.fn(cb, err, 'val');
+ };
+ const errorLogged = new Promise((resolve) => console.error.callsFake(resolve));
+ const call = () => callHookFnSync(hook);
+ if (step2.rejects) {
+ assert.throws(call, { message: 'val' });
+ }
+ else {
+ assert.equal(call(), 'val');
+ }
+ await errorLogged;
+ assert.equal(console.error.callCount, 1);
+ assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
+ });
+ }
}
- await errorLogged;
- assert.equal(console.error.callCount, 1);
- assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
- });
- }
- }
- });
- });
-
- describe('hooks.callAll', function () {
- describe('basic behavior', function () {
- it('calls all in order', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook(2), makeHook(3));
- assert.deepEqual(hooks.callAll(hookName), [1, 2, 3]);
- });
-
- it('passes hook name', async function () {
- hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
- hooks.callAll(hookName);
- });
-
- it('undefined context -> {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- hooks.callAll(hookName);
- });
-
- it('null context -> {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- hooks.callAll(hookName, null);
- });
-
- it('context unmodified', async function () {
- const wantContext = {};
- hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
- hooks.callAll(hookName, wantContext);
- });
- });
-
- describe('result processing', function () {
- it('no registered hooks (undefined) -> []', async function () {
- delete plugins.hooks.testHook;
- assert.deepEqual(hooks.callAll(hookName), []);
- });
-
- it('no registered hooks (empty list) -> []', async function () {
- testHooks.length = 0;
- assert.deepEqual(hooks.callAll(hookName), []);
- });
-
- it('flattens one level', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
- assert.deepEqual(hooks.callAll(hookName), [1, 2, [3]]);
- });
-
- it('filters out undefined', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]));
- assert.deepEqual(hooks.callAll(hookName), [2, [3]]);
- });
-
- it('preserves null', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(null), makeHook([2]), makeHook([[3]]));
- assert.deepEqual(hooks.callAll(hookName), [null, 2, [3]]);
- });
-
- it('all undefined -> []', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook());
- assert.deepEqual(hooks.callAll(hookName), []);
- });
- });
- });
-
- describe('hooks.callFirst', function () {
- it('no registered hooks (undefined) -> []', async function () {
- delete plugins.hooks.testHook;
- assert.deepEqual(hooks.callFirst(hookName), []);
- });
-
- it('no registered hooks (empty list) -> []', async function () {
- testHooks.length = 0;
- assert.deepEqual(hooks.callFirst(hookName), []);
- });
-
- it('passes hook name => {}', async function () {
- hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
- hooks.callFirst(hookName);
- });
-
- it('undefined context => {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- hooks.callFirst(hookName);
- });
-
- it('null context => {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- hooks.callFirst(hookName, null);
- });
-
- it('context unmodified', async function () {
- const wantContext = {};
- hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
- hooks.callFirst(hookName, wantContext);
- });
-
- it('predicate never satisfied -> calls all in order', async function () {
- const gotCalls = [];
- testHooks.length = 0;
- for (let i = 0; i < 3; i++) {
- const hook = makeHook();
- hook.hook_fn = () => { gotCalls.push(i); };
- testHooks.push(hook);
- }
- assert.deepEqual(hooks.callFirst(hookName), []);
- assert.deepEqual(gotCalls, [0, 1, 2]);
- });
-
- it('stops when predicate is satisfied', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
- assert.deepEqual(hooks.callFirst(hookName), ['val1']);
- });
-
- it('skips values that do not satisfy predicate (undefined)', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook('val1'));
- assert.deepEqual(hooks.callFirst(hookName), ['val1']);
- });
-
- it('skips values that do not satisfy predicate (empty list)', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook([]), makeHook('val1'));
- assert.deepEqual(hooks.callFirst(hookName), ['val1']);
- });
-
- it('null satisifes the predicate', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(null), makeHook('val1'));
- assert.deepEqual(hooks.callFirst(hookName), [null]);
- });
-
- it('non-empty arrays are returned unmodified', async function () {
- const want = ['val1'];
- testHooks.length = 0;
- testHooks.push(makeHook(want), makeHook(['val2']));
- assert.equal(hooks.callFirst(hookName), want); // Note: *NOT* deepEqual!
- });
-
- it('value can be passed via callback', async function () {
- const want = {};
- hook.hook_fn = (hn, ctx, cb) => { cb(want); };
- const got = hooks.callFirst(hookName);
- assert.deepEqual(got, [want]);
- assert.equal(got[0], want); // Note: *NOT* deepEqual!
+ });
});
- });
-
- describe('callHookFnAsync', function () {
- const callHookFnAsync = hooks.exportedForTestingOnly.callHookFnAsync; // Convenience shorthand.
-
- describe('basic behavior', function () {
- it('passes hook name', async function () {
- hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
- await callHookFnAsync(hook);
- });
-
- it('passes context', async function () {
- for (const val of ['value', null, undefined]) {
- hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
- await callHookFnAsync(hook, val);
- }
- });
-
- it('returns the value provided to the callback', async function () {
- for (const val of ['value', null, undefined]) {
- hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
- assert.equal(await callHookFnAsync(hook, val), val);
- assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
- }
- });
-
- it('returns the value returned by the hook function', async function () {
- for (const val of ['value', null, undefined]) {
- // Must not have the cb parameter otherwise returning undefined will never resolve.
- hook.hook_fn = (hn, ctx) => ctx;
- assert.equal(await callHookFnAsync(hook, val), val);
- assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
- }
- });
-
- it('rejects if it throws an exception', async function () {
- hook.hook_fn = () => { throw new Error('test exception'); };
- await assert.rejects(callHookFnAsync(hook), {message: 'test exception'});
- });
-
- it('rejects if rejected Promise passed to callback', async function () {
- hook.hook_fn = (hn, ctx, cb) => cb(Promise.reject(new Error('test exception')));
- await assert.rejects(callHookFnAsync(hook), {message: 'test exception'});
- });
-
- it('rejects if rejected Promise returned', async function () {
- hook.hook_fn = (hn, ctx, cb) => Promise.reject(new Error('test exception'));
- await assert.rejects(callHookFnAsync(hook), {message: 'test exception'});
- });
-
- it('callback returns undefined', async function () {
- hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
- await callHookFnAsync(hook);
- });
-
- it('checks for deprecation', async function () {
- sinon.stub(console, 'warn');
- hooks.deprecationNotices[hookName] = 'test deprecation';
- await callHookFnAsync(hook);
- assert.equal(hooks.exportedForTestingOnly.deprecationWarned[hookFnName], true);
- assert.equal(console.warn.callCount, 1);
- assert.match(console.warn.getCall(0).args[0], /test deprecation/);
- });
+ describe('hooks.callAll', function () {
+ describe('basic behavior', function () {
+ it('calls all in order', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook(2), makeHook(3));
+ assert.deepEqual(hooks.callAll(hookName), [1, 2, 3]);
+ });
+ it('passes hook name', async function () {
+ hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
+ hooks.callAll(hookName);
+ });
+ it('undefined context -> {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ hooks.callAll(hookName);
+ });
+ it('null context -> {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ hooks.callAll(hookName, null);
+ });
+ it('context unmodified', async function () {
+ const wantContext = {};
+ hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
+ hooks.callAll(hookName, wantContext);
+ });
+ });
+ describe('result processing', function () {
+ it('no registered hooks (undefined) -> []', async function () {
+ delete plugins.hooks.testHook;
+ assert.deepEqual(hooks.callAll(hookName), []);
+ });
+ it('no registered hooks (empty list) -> []', async function () {
+ testHooks.length = 0;
+ assert.deepEqual(hooks.callAll(hookName), []);
+ });
+ it('flattens one level', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
+ assert.deepEqual(hooks.callAll(hookName), [1, 2, [3]]);
+ });
+ it('filters out undefined', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]));
+ assert.deepEqual(hooks.callAll(hookName), [2, [3]]);
+ });
+ it('preserves null', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(null), makeHook([2]), makeHook([[3]]));
+ assert.deepEqual(hooks.callAll(hookName), [null, 2, [3]]);
+ });
+ it('all undefined -> []', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook());
+ assert.deepEqual(hooks.callAll(hookName), []);
+ });
+ });
});
-
- describe('supported hook function styles', function () {
- const supportedHookFunctions = supportedSyncHookFunctions.concat([
- {
- name: 'legacy async cb',
- fn: (hn, ctx, cb) => { process.nextTick(cb, 'val'); },
- want: 'val',
- },
- // Already resolved Promises:
- {
- name: 'return resolved Promise, with callback parameter',
- fn: (hn, ctx, cb) => Promise.resolve('val'),
- want: 'val',
- },
- {
- name: 'return resolved Promise, without callback parameter',
- fn: (hn, ctx) => Promise.resolve('val'),
- want: 'val',
- },
- {
- name: 'pass resolved Promise to callback',
- fn: (hn, ctx, cb) => { cb(Promise.resolve('val')); },
- want: 'val',
- },
- // Not yet resolved Promises:
- {
- name: 'return unresolved Promise, with callback parameter',
- fn: (hn, ctx, cb) => new Promise((resolve) => process.nextTick(resolve, 'val')),
- want: 'val',
- },
- {
- name: 'return unresolved Promise, without callback parameter',
- fn: (hn, ctx) => new Promise((resolve) => process.nextTick(resolve, 'val')),
- want: 'val',
- },
- {
- name: 'pass unresolved Promise to callback',
- fn: (hn, ctx, cb) => { cb(new Promise((resolve) => process.nextTick(resolve, 'val'))); },
- want: 'val',
- },
- // Already rejected Promises:
- {
- name: 'return rejected Promise, with callback parameter',
- fn: (hn, ctx, cb) => Promise.reject(new Error('test rejection')),
- wantErr: 'test rejection',
- },
- {
- name: 'return rejected Promise, without callback parameter',
- fn: (hn, ctx) => Promise.reject(new Error('test rejection')),
- wantErr: 'test rejection',
- },
- {
- name: 'pass rejected Promise to callback',
- fn: (hn, ctx, cb) => { cb(Promise.reject(new Error('test rejection'))); },
- wantErr: 'test rejection',
- },
- // Not yet rejected Promises:
- {
- name: 'return unrejected Promise, with callback parameter',
- fn: (hn, ctx, cb) => new Promise((resolve, reject) => {
- process.nextTick(reject, new Error('test rejection'));
- }),
- wantErr: 'test rejection',
- },
- {
- name: 'return unrejected Promise, without callback parameter',
- fn: (hn, ctx) => new Promise((resolve, reject) => {
- process.nextTick(reject, new Error('test rejection'));
- }),
- wantErr: 'test rejection',
- },
- {
- name: 'pass unrejected Promise to callback',
- fn: (hn, ctx, cb) => {
- cb(new Promise((resolve, reject) => {
- process.nextTick(reject, new Error('test rejection'));
- }));
- },
- wantErr: 'test rejection',
- },
- ]);
-
- for (const tc of supportedSyncHookFunctions.concat(supportedHookFunctions)) {
- it(tc.name, async function () {
- sinon.stub(console, 'warn');
- sinon.stub(console, 'error');
- hook.hook_fn = tc.fn;
- const p = callHookFnAsync(hook);
- if (tc.wantErr) {
- await assert.rejects(p, {message: tc.wantErr});
- } else {
- assert.equal(await p, tc.want);
- }
- assert.equal(console.warn.callCount, 0);
- assert.equal(console.error.callCount, 0);
+ describe('hooks.callFirst', function () {
+ it('no registered hooks (undefined) -> []', async function () {
+ delete plugins.hooks.testHook;
+ assert.deepEqual(hooks.callFirst(hookName), []);
+ });
+ it('no registered hooks (empty list) -> []', async function () {
+ testHooks.length = 0;
+ assert.deepEqual(hooks.callFirst(hookName), []);
+ });
+ it('passes hook name => {}', async function () {
+ hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
+ hooks.callFirst(hookName);
+ });
+ it('undefined context => {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ hooks.callFirst(hookName);
+ });
+ it('null context => {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ hooks.callFirst(hookName, null);
+ });
+ it('context unmodified', async function () {
+ const wantContext = {};
+ hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
+ hooks.callFirst(hookName, wantContext);
+ });
+ it('predicate never satisfied -> calls all in order', async function () {
+ const gotCalls = [];
+ testHooks.length = 0;
+ for (let i = 0; i < 3; i++) {
+ const hook = makeHook();
+ hook.hook_fn = () => { gotCalls.push(i); };
+ testHooks.push(hook);
+ }
+ assert.deepEqual(hooks.callFirst(hookName), []);
+ assert.deepEqual(gotCalls, [0, 1, 2]);
+ });
+ it('stops when predicate is satisfied', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
+ assert.deepEqual(hooks.callFirst(hookName), ['val1']);
+ });
+ it('skips values that do not satisfy predicate (undefined)', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook('val1'));
+ assert.deepEqual(hooks.callFirst(hookName), ['val1']);
+ });
+ it('skips values that do not satisfy predicate (empty list)', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook([]), makeHook('val1'));
+ assert.deepEqual(hooks.callFirst(hookName), ['val1']);
+ });
+ it('null satisifes the predicate', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(null), makeHook('val1'));
+ assert.deepEqual(hooks.callFirst(hookName), [null]);
+ });
+ it('non-empty arrays are returned unmodified', async function () {
+ const want = ['val1'];
+ testHooks.length = 0;
+ testHooks.push(makeHook(want), makeHook(['val2']));
+ assert.equal(hooks.callFirst(hookName), want); // Note: *NOT* deepEqual!
+ });
+ it('value can be passed via callback', async function () {
+ const want = {};
+ hook.hook_fn = (hn, ctx, cb) => { cb(want); };
+ const got = hooks.callFirst(hookName);
+ assert.deepEqual(got, [want]);
+ assert.equal(got[0], want); // Note: *NOT* deepEqual!
});
- }
});
-
- // Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
- // time, or call the callback and then return a value.)
- describe('bad hook function behavior (double settle)', function () {
- beforeEach(async function () {
- sinon.stub(console, 'error');
- });
-
- // Each item in this array codifies a way to settle an asynchronous hook function. Each of the
- // test cases below combines two of these behaviors in a single hook function and confirms
- // that callHookFnAsync both (1) resolves to the result of the first settle attempt, and (2)
- // detects the second settle attempt.
- //
- // The 'when' property specifies the relative time that two behaviors will cause the hook
- // function to settle:
- // * If behavior1.when <= behavior2.when and behavior1 is called before behavior2 then
- // behavior1 will settle the hook function before behavior2.
- // * Otherwise, behavior2 will settle the hook function before behavior1.
- const behaviors = [
- {
- name: 'throw',
- fn: (cb, err, val) => { throw err; },
- rejects: true,
- when: 0,
- },
- {
- name: 'return value',
- fn: (cb, err, val) => val,
- // This behavior has a later relative settle time vs. the 'throw' behavior because 'throw'
- // immediately settles the hook function, whereas the 'return value' case is settled by a
- // .then() function attached to a Promise. EcmaScript guarantees that a .then() function
- // attached to a Promise is enqueued on the event loop (not executed immediately) when the
- // Promise settles.
- when: 1,
- },
- {
- name: 'immediately call cb(value)',
- fn: (cb, err, val) => cb(val),
- // This behavior has the same relative time as the 'return value' case because it too is
- // settled by a .then() function attached to a Promise.
- when: 1,
- },
- {
- name: 'return resolvedPromise',
- fn: (cb, err, val) => Promise.resolve(val),
- // This behavior has the same relative time as the 'return value' case because the return
- // value is wrapped in a Promise via Promise.resolve(). The EcmaScript standard guarantees
- // that Promise.resolve(Promise.resolve(value)) is equivalent to Promise.resolve(value),
- // so returning an already resolved Promise vs. returning a non-Promise value are
- // equivalent.
- when: 1,
- },
- {
- name: 'immediately call cb(resolvedPromise)',
- fn: (cb, err, val) => cb(Promise.resolve(val)),
- when: 1,
- },
- {
- name: 'return rejectedPromise',
- fn: (cb, err, val) => Promise.reject(err),
- rejects: true,
- when: 1,
- },
- {
- name: 'immediately call cb(rejectedPromise)',
- fn: (cb, err, val) => cb(Promise.reject(err)),
- rejects: true,
- when: 1,
- },
- {
- name: 'return unresolvedPromise',
- fn: (cb, err, val) => new Promise((resolve) => process.nextTick(resolve, val)),
- when: 2,
- },
- {
- name: 'immediately call cb(unresolvedPromise)',
- fn: (cb, err, val) => cb(new Promise((resolve) => process.nextTick(resolve, val))),
- when: 2,
- },
- {
- name: 'return unrejectedPromise',
- fn: (cb, err, val) => new Promise((resolve, reject) => process.nextTick(reject, err)),
- rejects: true,
- when: 2,
- },
- {
- name: 'immediately call cb(unrejectedPromise)',
- fn: (cb, err, val) => cb(new Promise((resolve, reject) => process.nextTick(reject, err))),
- rejects: true,
- when: 2,
- },
- {
- name: 'defer call to cb(value)',
- fn: (cb, err, val) => { process.nextTick(cb, val); },
- when: 2,
- },
- {
- name: 'defer call to cb(resolvedPromise)',
- fn: (cb, err, val) => { process.nextTick(cb, Promise.resolve(val)); },
- when: 2,
- },
- {
- name: 'defer call to cb(rejectedPromise)',
- fn: (cb, err, val) => { process.nextTick(cb, Promise.reject(err)); },
- rejects: true,
- when: 2,
- },
- {
- name: 'defer call to cb(unresolvedPromise)',
- fn: (cb, err, val) => {
- process.nextTick(() => {
- cb(new Promise((resolve) => process.nextTick(resolve, val)));
+ describe('callHookFnAsync', function () {
+ const callHookFnAsync = hooks.exportedForTestingOnly.callHookFnAsync; // Convenience shorthand.
+ describe('basic behavior', function () {
+ it('passes hook name', async function () {
+ hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
+ await callHookFnAsync(hook);
});
- },
- when: 3,
- },
- {
- name: 'defer call cb(unrejectedPromise)',
- fn: (cb, err, val) => {
- process.nextTick(() => {
- cb(new Promise((resolve, reject) => process.nextTick(reject, err)));
+ it('passes context', async function () {
+ for (const val of ['value', null, undefined]) {
+ hook.hook_fn = (hn, ctx) => { assert.equal(ctx, val); };
+ await callHookFnAsync(hook, val);
+ }
});
- },
- rejects: true,
- when: 3,
- },
- ];
-
- for (const step1 of behaviors) {
- // There can't be a second step if the first step is to return or throw.
- if (step1.name.startsWith('return ') || step1.name === 'throw') continue;
- for (const step2 of behaviors) {
- it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
- hook.hook_fn = (hn, ctx, cb) => {
- step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
- return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
- };
-
- // Temporarily remove unhandled Promise rejection listeners so that the unhandled
- // rejections we expect to see don't trigger a test failure (or terminate node).
- const event = 'unhandledRejection';
- const listenersBackup = process.rawListeners(event);
- process.removeAllListeners(event);
-
- let tempListener;
- let asyncErr;
- try {
- const seenErrPromise = new Promise((resolve) => {
- tempListener = (err) => {
- assert.equal(asyncErr, undefined);
- asyncErr = err;
- resolve();
- };
- });
- process.on(event, tempListener);
- const step1Wins = step1.when <= step2.when;
- const winningStep = step1Wins ? step1 : step2;
- const winningVal = step1Wins ? 'val1' : 'val2';
- const p = callHookFnAsync(hook, {ret1: 'val1', ret2: 'val2'});
- if (winningStep.rejects) {
- await assert.rejects(p, {message: winningVal});
- } else {
- assert.equal(await p, winningVal);
- }
- await seenErrPromise;
- } finally {
- // Restore the original listeners.
- process.off(event, tempListener);
- for (const listener of listenersBackup) {
- process.on(event, listener);
- }
+ it('returns the value provided to the callback', async function () {
+ for (const val of ['value', null, undefined]) {
+ hook.hook_fn = (hn, ctx, cb) => { cb(ctx); };
+ assert.equal(await callHookFnAsync(hook, val), val);
+ assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
+ }
+ });
+ it('returns the value returned by the hook function', async function () {
+ for (const val of ['value', null, undefined]) {
+ // Must not have the cb parameter otherwise returning undefined will never resolve.
+ hook.hook_fn = (hn, ctx) => ctx;
+ assert.equal(await callHookFnAsync(hook, val), val);
+ assert.equal(await callHookFnAsync(hook, Promise.resolve(val)), val);
+ }
+ });
+ it('rejects if it throws an exception', async function () {
+ hook.hook_fn = () => { throw new Error('test exception'); };
+ await assert.rejects(callHookFnAsync(hook), { message: 'test exception' });
+ });
+ it('rejects if rejected Promise passed to callback', async function () {
+ hook.hook_fn = (hn, ctx, cb) => cb(Promise.reject(new Error('test exception')));
+ await assert.rejects(callHookFnAsync(hook), { message: 'test exception' });
+ });
+ it('rejects if rejected Promise returned', async function () {
+ hook.hook_fn = (hn, ctx, cb) => Promise.reject(new Error('test exception'));
+ await assert.rejects(callHookFnAsync(hook), { message: 'test exception' });
+ });
+ it('callback returns undefined', async function () {
+ hook.hook_fn = (hn, ctx, cb) => { assert.equal(cb('foo'), undefined); };
+ await callHookFnAsync(hook);
+ });
+ it('checks for deprecation', async function () {
+ sinon.stub(console, 'warn');
+ hooks.deprecationNotices[hookName] = 'test deprecation';
+ await callHookFnAsync(hook);
+ assert.equal(hooks.exportedForTestingOnly.deprecationWarned[hookFnName], true);
+ assert.equal(console.warn.callCount, 1);
+ assert.match(console.warn.getCall(0).args[0], /test deprecation/);
+ });
+ });
+ describe('supported hook function styles', function () {
+ const supportedHookFunctions = supportedSyncHookFunctions.concat([
+ {
+ name: 'legacy async cb',
+ fn: (hn, ctx, cb) => { process.nextTick(cb, 'val'); },
+ want: 'val',
+ },
+ // Already resolved Promises:
+ {
+ name: 'return resolved Promise, with callback parameter',
+ fn: (hn, ctx, cb) => Promise.resolve('val'),
+ want: 'val',
+ },
+ {
+ name: 'return resolved Promise, without callback parameter',
+ fn: (hn, ctx) => Promise.resolve('val'),
+ want: 'val',
+ },
+ {
+ name: 'pass resolved Promise to callback',
+ fn: (hn, ctx, cb) => { cb(Promise.resolve('val')); },
+ want: 'val',
+ },
+ // Not yet resolved Promises:
+ {
+ name: 'return unresolved Promise, with callback parameter',
+ fn: (hn, ctx, cb) => new Promise((resolve) => process.nextTick(resolve, 'val')),
+ want: 'val',
+ },
+ {
+ name: 'return unresolved Promise, without callback parameter',
+ fn: (hn, ctx) => new Promise((resolve) => process.nextTick(resolve, 'val')),
+ want: 'val',
+ },
+ {
+ name: 'pass unresolved Promise to callback',
+ fn: (hn, ctx, cb) => { cb(new Promise((resolve) => process.nextTick(resolve, 'val'))); },
+ want: 'val',
+ },
+ // Already rejected Promises:
+ {
+ name: 'return rejected Promise, with callback parameter',
+ fn: (hn, ctx, cb) => Promise.reject(new Error('test rejection')),
+ wantErr: 'test rejection',
+ },
+ {
+ name: 'return rejected Promise, without callback parameter',
+ fn: (hn, ctx) => Promise.reject(new Error('test rejection')),
+ wantErr: 'test rejection',
+ },
+ {
+ name: 'pass rejected Promise to callback',
+ fn: (hn, ctx, cb) => { cb(Promise.reject(new Error('test rejection'))); },
+ wantErr: 'test rejection',
+ },
+ // Not yet rejected Promises:
+ {
+ name: 'return unrejected Promise, with callback parameter',
+ fn: (hn, ctx, cb) => new Promise((resolve, reject) => {
+ process.nextTick(reject, new Error('test rejection'));
+ }),
+ wantErr: 'test rejection',
+ },
+ {
+ name: 'return unrejected Promise, without callback parameter',
+ fn: (hn, ctx) => new Promise((resolve, reject) => {
+ process.nextTick(reject, new Error('test rejection'));
+ }),
+ wantErr: 'test rejection',
+ },
+ {
+ name: 'pass unrejected Promise to callback',
+ fn: (hn, ctx, cb) => {
+ cb(new Promise((resolve, reject) => {
+ process.nextTick(reject, new Error('test rejection'));
+ }));
+ },
+ wantErr: 'test rejection',
+ },
+ ]);
+ for (const tc of supportedSyncHookFunctions.concat(supportedHookFunctions)) {
+ it(tc.name, async function () {
+ sinon.stub(console, 'warn');
+ sinon.stub(console, 'error');
+ hook.hook_fn = tc.fn;
+ const p = callHookFnAsync(hook);
+ if (tc.wantErr) {
+ await assert.rejects(p, { message: tc.wantErr });
+ }
+ else {
+ assert.equal(await p, tc.want);
+ }
+ assert.equal(console.warn.callCount, 0);
+ assert.equal(console.error.callCount, 0);
+ });
}
- assert.equal(console.error.callCount, 1,
- `Got errors:\n${
- console.error.getCalls().map((call) => call.args[0]).join('\n')}`);
- assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
- assert(asyncErr instanceof Error);
- assert.match(asyncErr.message, /DOUBLE SETTLE BUG/);
- });
-
- // This next test is the same as the above test, except the second settle attempt is for
- // the same outcome. The two outcomes can't be the same if one step rejects and the other
- // doesn't, so skip those cases.
- if (step1.rejects !== step2.rejects) continue;
-
- it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
- const err = new Error('val');
- hook.hook_fn = (hn, ctx, cb) => {
- step1.fn(cb, err, 'val');
- return step2.fn(cb, err, 'val');
- };
- const winningStep = (step1.when <= step2.when) ? step1 : step2;
- const errorLogged = new Promise((resolve) => console.error.callsFake(resolve));
- const p = callHookFnAsync(hook);
- if (winningStep.rejects) {
- await assert.rejects(p, {message: 'val'});
- } else {
- assert.equal(await p, 'val');
+ });
+ // Test various ways a hook might attempt to settle twice. (Examples: call the callback a second
+ // time, or call the callback and then return a value.)
+ describe('bad hook function behavior (double settle)', function () {
+ beforeEach(async function () {
+ sinon.stub(console, 'error');
+ });
+ // Each item in this array codifies a way to settle an asynchronous hook function. Each of the
+ // test cases below combines two of these behaviors in a single hook function and confirms
+ // that callHookFnAsync both (1) resolves to the result of the first settle attempt, and (2)
+ // detects the second settle attempt.
+ //
+ // The 'when' property specifies the relative time that two behaviors will cause the hook
+ // function to settle:
+ // * If behavior1.when <= behavior2.when and behavior1 is called before behavior2 then
+ // behavior1 will settle the hook function before behavior2.
+ // * Otherwise, behavior2 will settle the hook function before behavior1.
+ const behaviors = [
+ {
+ name: 'throw',
+ fn: (cb, err, val) => { throw err; },
+ rejects: true,
+ when: 0,
+ },
+ {
+ name: 'return value',
+ fn: (cb, err, val) => val,
+ // This behavior has a later relative settle time vs. the 'throw' behavior because 'throw'
+ // immediately settles the hook function, whereas the 'return value' case is settled by a
+ // .then() function attached to a Promise. EcmaScript guarantees that a .then() function
+ // attached to a Promise is enqueued on the event loop (not executed immediately) when the
+ // Promise settles.
+ when: 1,
+ },
+ {
+ name: 'immediately call cb(value)',
+ fn: (cb, err, val) => cb(val),
+ // This behavior has the same relative time as the 'return value' case because it too is
+ // settled by a .then() function attached to a Promise.
+ when: 1,
+ },
+ {
+ name: 'return resolvedPromise',
+ fn: (cb, err, val) => Promise.resolve(val),
+ // This behavior has the same relative time as the 'return value' case because the return
+ // value is wrapped in a Promise via Promise.resolve(). The EcmaScript standard guarantees
+ // that Promise.resolve(Promise.resolve(value)) is equivalent to Promise.resolve(value),
+ // so returning an already resolved Promise vs. returning a non-Promise value are
+ // equivalent.
+ when: 1,
+ },
+ {
+ name: 'immediately call cb(resolvedPromise)',
+ fn: (cb, err, val) => cb(Promise.resolve(val)),
+ when: 1,
+ },
+ {
+ name: 'return rejectedPromise',
+ fn: (cb, err, val) => Promise.reject(err),
+ rejects: true,
+ when: 1,
+ },
+ {
+ name: 'immediately call cb(rejectedPromise)',
+ fn: (cb, err, val) => cb(Promise.reject(err)),
+ rejects: true,
+ when: 1,
+ },
+ {
+ name: 'return unresolvedPromise',
+ fn: (cb, err, val) => new Promise((resolve) => process.nextTick(resolve, val)),
+ when: 2,
+ },
+ {
+ name: 'immediately call cb(unresolvedPromise)',
+ fn: (cb, err, val) => cb(new Promise((resolve) => process.nextTick(resolve, val))),
+ when: 2,
+ },
+ {
+ name: 'return unrejectedPromise',
+ fn: (cb, err, val) => new Promise((resolve, reject) => process.nextTick(reject, err)),
+ rejects: true,
+ when: 2,
+ },
+ {
+ name: 'immediately call cb(unrejectedPromise)',
+ fn: (cb, err, val) => cb(new Promise((resolve, reject) => process.nextTick(reject, err))),
+ rejects: true,
+ when: 2,
+ },
+ {
+ name: 'defer call to cb(value)',
+ fn: (cb, err, val) => { process.nextTick(cb, val); },
+ when: 2,
+ },
+ {
+ name: 'defer call to cb(resolvedPromise)',
+ fn: (cb, err, val) => { process.nextTick(cb, Promise.resolve(val)); },
+ when: 2,
+ },
+ {
+ name: 'defer call to cb(rejectedPromise)',
+ fn: (cb, err, val) => { process.nextTick(cb, Promise.reject(err)); },
+ rejects: true,
+ when: 2,
+ },
+ {
+ name: 'defer call to cb(unresolvedPromise)',
+ fn: (cb, err, val) => {
+ process.nextTick(() => {
+ cb(new Promise((resolve) => process.nextTick(resolve, val)));
+ });
+ },
+ when: 3,
+ },
+ {
+ name: 'defer call cb(unrejectedPromise)',
+ fn: (cb, err, val) => {
+ process.nextTick(() => {
+ cb(new Promise((resolve, reject) => process.nextTick(reject, err)));
+ });
+ },
+ rejects: true,
+ when: 3,
+ },
+ ];
+ for (const step1 of behaviors) {
+ // There can't be a second step if the first step is to return or throw.
+ if (step1.name.startsWith('return ') || step1.name === 'throw')
+ continue;
+ for (const step2 of behaviors) {
+ it(`${step1.name} then ${step2.name} (diff. outcomes) -> log+throw`, async function () {
+ hook.hook_fn = (hn, ctx, cb) => {
+ step1.fn(cb, new Error(ctx.ret1), ctx.ret1);
+ return step2.fn(cb, new Error(ctx.ret2), ctx.ret2);
+ };
+ // Temporarily remove unhandled Promise rejection listeners so that the unhandled
+ // rejections we expect to see don't trigger a test failure (or terminate node).
+ const event = 'unhandledRejection';
+ const listenersBackup = process.rawListeners(event);
+ process.removeAllListeners(event);
+ let tempListener;
+ let asyncErr;
+ try {
+ const seenErrPromise = new Promise((resolve) => {
+ tempListener = (err) => {
+ assert.equal(asyncErr, undefined);
+ asyncErr = err;
+ resolve();
+ };
+ });
+ process.on(event, tempListener);
+ const step1Wins = step1.when <= step2.when;
+ const winningStep = step1Wins ? step1 : step2;
+ const winningVal = step1Wins ? 'val1' : 'val2';
+ const p = callHookFnAsync(hook, { ret1: 'val1', ret2: 'val2' });
+ if (winningStep.rejects) {
+ await assert.rejects(p, { message: winningVal });
+ }
+ else {
+ assert.equal(await p, winningVal);
+ }
+ await seenErrPromise;
+ }
+ finally {
+ // Restore the original listeners.
+ process.off(event, tempListener);
+ for (const listener of listenersBackup) {
+ process.on(event, listener);
+ }
+ }
+ assert.equal(console.error.callCount, 1, `Got errors:\n${console.error.getCalls().map((call) => call.args[0]).join('\n')}`);
+ assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
+ assert(asyncErr instanceof Error);
+ assert.match(asyncErr.message, /DOUBLE SETTLE BUG/);
+ });
+ // This next test is the same as the above test, except the second settle attempt is for
+ // the same outcome. The two outcomes can't be the same if one step rejects and the other
+ // doesn't, so skip those cases.
+ if (step1.rejects !== step2.rejects)
+ continue;
+ it(`${step1.name} then ${step2.name} (same outcome) -> only log`, async function () {
+ const err = new Error('val');
+ hook.hook_fn = (hn, ctx, cb) => {
+ step1.fn(cb, err, 'val');
+ return step2.fn(cb, err, 'val');
+ };
+ const winningStep = (step1.when <= step2.when) ? step1 : step2;
+ const errorLogged = new Promise((resolve) => console.error.callsFake(resolve));
+ const p = callHookFnAsync(hook);
+ if (winningStep.rejects) {
+ await assert.rejects(p, { message: 'val' });
+ }
+ else {
+ assert.equal(await p, 'val');
+ }
+ await errorLogged;
+ assert.equal(console.error.callCount, 1);
+ assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
+ });
+ }
}
- await errorLogged;
- assert.equal(console.error.callCount, 1);
- assert.match(console.error.getCall(0).args[0], /DOUBLE SETTLE BUG/);
- });
- }
- }
- });
- });
-
- describe('hooks.aCallAll', function () {
- describe('basic behavior', function () {
- it('calls all asynchronously, returns values in order', async function () {
- testHooks.length = 0; // Delete the boilerplate hook -- this test doesn't use it.
- let nextIndex = 0;
- const hookPromises = [];
- const hookStarted = [];
- const hookFinished = [];
- const makeHook = () => {
- const i = nextIndex++;
- const entry = {};
- hookStarted[i] = false;
- hookFinished[i] = false;
- hookPromises[i] = entry;
- entry.promise = new Promise((resolve) => {
- entry.resolve = () => {
- hookFinished[i] = true;
- resolve(i);
- };
- });
- return {hook_fn: () => {
- hookStarted[i] = true;
- return entry.promise;
- }};
- };
- testHooks.push(makeHook(), makeHook());
- const p = hooks.aCallAll(hookName);
- assert.deepEqual(hookStarted, [true, true]);
- assert.deepEqual(hookFinished, [false, false]);
- hookPromises[1].resolve();
- await hookPromises[1].promise;
- assert.deepEqual(hookFinished, [false, true]);
- hookPromises[0].resolve();
- assert.deepEqual(await p, [0, 1]);
- });
-
- it('passes hook name', async function () {
- hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
- await hooks.aCallAll(hookName);
- });
-
- it('undefined context -> {}', async function () {
- hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.aCallAll(hookName);
- });
-
- it('null context -> {}', async function () {
- hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.aCallAll(hookName, null);
- });
-
- it('context unmodified', async function () {
- const wantContext = {};
- hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
- await hooks.aCallAll(hookName, wantContext);
- });
+ });
});
-
- describe('aCallAll callback', function () {
- it('exception in callback rejects', async function () {
- const p = hooks.aCallAll(hookName, {}, () => { throw new Error('test exception'); });
- await assert.rejects(p, {message: 'test exception'});
- });
-
- it('propagates error on exception', async function () {
- hook.hook_fn = () => { throw new Error('test exception'); };
- await hooks.aCallAll(hookName, {}, (err) => {
- assert(err instanceof Error);
- assert.equal(err.message, 'test exception');
+ describe('hooks.aCallAll', function () {
+ describe('basic behavior', function () {
+ it('calls all asynchronously, returns values in order', async function () {
+ testHooks.length = 0; // Delete the boilerplate hook -- this test doesn't use it.
+ let nextIndex = 0;
+ const hookPromises = [];
+ const hookStarted = [];
+ const hookFinished = [];
+ const makeHook = () => {
+ const i = nextIndex++;
+ const entry = {};
+ hookStarted[i] = false;
+ hookFinished[i] = false;
+ hookPromises[i] = entry;
+ entry.promise = new Promise((resolve) => {
+ entry.resolve = () => {
+ hookFinished[i] = true;
+ resolve(i);
+ };
+ });
+ return { hook_fn: () => {
+ hookStarted[i] = true;
+ return entry.promise;
+ } };
+ };
+ testHooks.push(makeHook(), makeHook());
+ const p = hooks.aCallAll(hookName);
+ assert.deepEqual(hookStarted, [true, true]);
+ assert.deepEqual(hookFinished, [false, false]);
+ hookPromises[1].resolve();
+ await hookPromises[1].promise;
+ assert.deepEqual(hookFinished, [false, true]);
+ hookPromises[0].resolve();
+ assert.deepEqual(await p, [0, 1]);
+ });
+ it('passes hook name', async function () {
+ hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
+ await hooks.aCallAll(hookName);
+ });
+ it('undefined context -> {}', async function () {
+ hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.aCallAll(hookName);
+ });
+ it('null context -> {}', async function () {
+ hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.aCallAll(hookName, null);
+ });
+ it('context unmodified', async function () {
+ const wantContext = {};
+ hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
+ await hooks.aCallAll(hookName, wantContext);
+ });
});
- });
-
- it('propagages null error on success', async function () {
- await hooks.aCallAll(hookName, {}, (err) => {
- assert(err == null, `got non-null error: ${err}`);
+ describe('aCallAll callback', function () {
+ it('exception in callback rejects', async function () {
+ const p = hooks.aCallAll(hookName, {}, () => { throw new Error('test exception'); });
+ await assert.rejects(p, { message: 'test exception' });
+ });
+ it('propagates error on exception', async function () {
+ hook.hook_fn = () => { throw new Error('test exception'); };
+ await hooks.aCallAll(hookName, {}, (err) => {
+ assert(err instanceof Error);
+ assert.equal(err.message, 'test exception');
+ });
+ });
+ it('propagages null error on success', async function () {
+ await hooks.aCallAll(hookName, {}, (err) => {
+ assert(err == null, `got non-null error: ${err}`);
+ });
+ });
+ it('propagages results on success', async function () {
+ hook.hook_fn = () => 'val';
+ await hooks.aCallAll(hookName, {}, (err, results) => {
+ assert.deepEqual(results, ['val']);
+ });
+ });
+ it('returns callback return value', async function () {
+ assert.equal(await hooks.aCallAll(hookName, {}, () => 'val'), 'val');
+ });
});
- });
-
- it('propagages results on success', async function () {
- hook.hook_fn = () => 'val';
- await hooks.aCallAll(hookName, {}, (err, results) => {
- assert.deepEqual(results, ['val']);
+ describe('result processing', function () {
+ it('no registered hooks (undefined) -> []', async function () {
+ delete plugins.hooks[hookName];
+ assert.deepEqual(await hooks.aCallAll(hookName), []);
+ });
+ it('no registered hooks (empty list) -> []', async function () {
+ testHooks.length = 0;
+ assert.deepEqual(await hooks.aCallAll(hookName), []);
+ });
+ it('flattens one level', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
+ assert.deepEqual(await hooks.aCallAll(hookName), [1, 2, [3]]);
+ });
+ it('filters out undefined', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]), makeHook(Promise.resolve()));
+ assert.deepEqual(await hooks.aCallAll(hookName), [2, [3]]);
+ });
+ it('preserves null', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(null), makeHook([2]), makeHook(Promise.resolve(null)));
+ assert.deepEqual(await hooks.aCallAll(hookName), [null, 2, null]);
+ });
+ it('all undefined -> []', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook(Promise.resolve()));
+ assert.deepEqual(await hooks.aCallAll(hookName), []);
+ });
});
- });
-
- it('returns callback return value', async function () {
- assert.equal(await hooks.aCallAll(hookName, {}, () => 'val'), 'val');
- });
- });
-
- describe('result processing', function () {
- it('no registered hooks (undefined) -> []', async function () {
- delete plugins.hooks[hookName];
- assert.deepEqual(await hooks.aCallAll(hookName), []);
- });
-
- it('no registered hooks (empty list) -> []', async function () {
- testHooks.length = 0;
- assert.deepEqual(await hooks.aCallAll(hookName), []);
- });
-
- it('flattens one level', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
- assert.deepEqual(await hooks.aCallAll(hookName), [1, 2, [3]]);
- });
-
- it('filters out undefined', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]), makeHook(Promise.resolve()));
- assert.deepEqual(await hooks.aCallAll(hookName), [2, [3]]);
- });
-
- it('preserves null', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(null), makeHook([2]), makeHook(Promise.resolve(null)));
- assert.deepEqual(await hooks.aCallAll(hookName), [null, 2, null]);
- });
-
- it('all undefined -> []', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook(Promise.resolve()));
- assert.deepEqual(await hooks.aCallAll(hookName), []);
- });
});
- });
-
- describe('hooks.callAllSerial', function () {
- describe('basic behavior', function () {
- it('calls all asynchronously, serially, in order', async function () {
- const gotCalls = [];
- testHooks.length = 0;
- for (let i = 0; i < 3; i++) {
- const hook = makeHook();
- hook.hook_fn = async () => {
- gotCalls.push(i);
- // Check gotCalls asynchronously to ensure that the next hook function does not start
- // executing before this hook function has resolved.
- return await new Promise((resolve) => {
- setImmediate(() => {
- assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
- resolve(i);
- });
+ describe('hooks.callAllSerial', function () {
+ describe('basic behavior', function () {
+ it('calls all asynchronously, serially, in order', async function () {
+ const gotCalls = [];
+ testHooks.length = 0;
+ for (let i = 0; i < 3; i++) {
+ const hook = makeHook();
+ hook.hook_fn = async () => {
+ gotCalls.push(i);
+ // Check gotCalls asynchronously to ensure that the next hook function does not start
+ // executing before this hook function has resolved.
+ return await new Promise((resolve) => {
+ setImmediate(() => {
+ assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
+ resolve(i);
+ });
+ });
+ };
+ testHooks.push(hook);
+ }
+ assert.deepEqual(await hooks.callAllSerial(hookName), [0, 1, 2]);
+ assert.deepEqual(gotCalls, [0, 1, 2]);
});
- };
- testHooks.push(hook);
- }
- assert.deepEqual(await hooks.callAllSerial(hookName), [0, 1, 2]);
- assert.deepEqual(gotCalls, [0, 1, 2]);
- });
-
- it('passes hook name', async function () {
- hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
- await hooks.callAllSerial(hookName);
- });
-
- it('undefined context -> {}', async function () {
- hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.callAllSerial(hookName);
- });
-
- it('null context -> {}', async function () {
- hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.callAllSerial(hookName, null);
- });
-
- it('context unmodified', async function () {
- const wantContext = {};
- hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
- await hooks.callAllSerial(hookName, wantContext);
- });
- });
-
- describe('result processing', function () {
- it('no registered hooks (undefined) -> []', async function () {
- delete plugins.hooks[hookName];
- assert.deepEqual(await hooks.callAllSerial(hookName), []);
- });
-
- it('no registered hooks (empty list) -> []', async function () {
- testHooks.length = 0;
- assert.deepEqual(await hooks.callAllSerial(hookName), []);
- });
-
- it('flattens one level', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
- assert.deepEqual(await hooks.callAllSerial(hookName), [1, 2, [3]]);
- });
-
- it('filters out undefined', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]), makeHook(Promise.resolve()));
- assert.deepEqual(await hooks.callAllSerial(hookName), [2, [3]]);
- });
-
- it('preserves null', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(null), makeHook([2]), makeHook(Promise.resolve(null)));
- assert.deepEqual(await hooks.callAllSerial(hookName), [null, 2, null]);
- });
-
- it('all undefined -> []', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook(Promise.resolve()));
- assert.deepEqual(await hooks.callAllSerial(hookName), []);
- });
- });
- });
-
- describe('hooks.aCallFirst', function () {
- it('no registered hooks (undefined) -> []', async function () {
- delete plugins.hooks.testHook;
- assert.deepEqual(await hooks.aCallFirst(hookName), []);
- });
-
- it('no registered hooks (empty list) -> []', async function () {
- testHooks.length = 0;
- assert.deepEqual(await hooks.aCallFirst(hookName), []);
- });
-
- it('passes hook name => {}', async function () {
- hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
- await hooks.aCallFirst(hookName);
- });
-
- it('undefined context => {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.aCallFirst(hookName);
- });
-
- it('null context => {}', async function () {
- hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
- await hooks.aCallFirst(hookName, null);
- });
-
- it('context unmodified', async function () {
- const wantContext = {};
- hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
- await hooks.aCallFirst(hookName, wantContext);
- });
-
- it('default predicate: predicate never satisfied -> calls all in order', async function () {
- const gotCalls = [];
- testHooks.length = 0;
- for (let i = 0; i < 3; i++) {
- const hook = makeHook();
- hook.hook_fn = () => { gotCalls.push(i); };
- testHooks.push(hook);
- }
- assert.deepEqual(await hooks.aCallFirst(hookName), []);
- assert.deepEqual(gotCalls, [0, 1, 2]);
- });
-
- it('calls hook functions serially', async function () {
- const gotCalls = [];
- testHooks.length = 0;
- for (let i = 0; i < 3; i++) {
- const hook = makeHook();
- hook.hook_fn = async () => {
- gotCalls.push(i);
- // Check gotCalls asynchronously to ensure that the next hook function does not start
- // executing before this hook function has resolved.
- return await new Promise((resolve) => {
- setImmediate(() => {
- assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
- resolve();
+ it('passes hook name', async function () {
+ hook.hook_fn = async (hn) => { assert.equal(hn, hookName); };
+ await hooks.callAllSerial(hookName);
});
- });
- };
- testHooks.push(hook);
- }
- assert.deepEqual(await hooks.aCallFirst(hookName), []);
- assert.deepEqual(gotCalls, [0, 1, 2]);
- });
-
- it('default predicate: stops when satisfied', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
- assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
- });
-
- it('default predicate: skips values that do not satisfy (undefined)', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(), makeHook('val1'));
- assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
- });
-
- it('default predicate: skips values that do not satisfy (empty list)', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook([]), makeHook('val1'));
- assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
- });
-
- it('default predicate: null satisifes', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(null), makeHook('val1'));
- assert.deepEqual(await hooks.aCallFirst(hookName), [null]);
- });
-
- it('custom predicate: called for each hook function', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(0), makeHook(1), makeHook(2));
- let got = 0;
- await hooks.aCallFirst(hookName, null, null, (val) => { ++got; return false; });
- assert.equal(got, 3);
- });
-
- it('custom predicate: boolean false/true continues/stops iteration', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook(2), makeHook(3));
- let nCall = 0;
- const predicate = (val) => {
- assert.deepEqual(val, [++nCall]);
- return nCall === 2;
- };
- assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
- assert.equal(nCall, 2);
- });
-
- it('custom predicate: non-boolean falsy/truthy continues/stops iteration', async function () {
- testHooks.length = 0;
- testHooks.push(makeHook(1), makeHook(2), makeHook(3));
- let nCall = 0;
- const predicate = (val) => {
- assert.deepEqual(val, [++nCall]);
- return nCall === 2 ? {} : null;
- };
- assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
- assert.equal(nCall, 2);
- });
-
- it('custom predicate: array value passed unmodified to predicate', async function () {
- const want = [0];
- hook.hook_fn = () => want;
- const predicate = (got) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
- await hooks.aCallFirst(hookName, null, null, predicate);
- });
-
- it('custom predicate: normalized value passed to predicate (undefined)', async function () {
- const predicate = (got) => { assert.deepEqual(got, []); };
- await hooks.aCallFirst(hookName, null, null, predicate);
- });
-
- it('custom predicate: normalized value passed to predicate (null)', async function () {
- hook.hook_fn = () => null;
- const predicate = (got) => { assert.deepEqual(got, [null]); };
- await hooks.aCallFirst(hookName, null, null, predicate);
- });
-
- it('non-empty arrays are returned unmodified', async function () {
- const want = ['val1'];
- testHooks.length = 0;
- testHooks.push(makeHook(want), makeHook(['val2']));
- assert.equal(await hooks.aCallFirst(hookName), want); // Note: *NOT* deepEqual!
+ it('undefined context -> {}', async function () {
+ hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.callAllSerial(hookName);
+ });
+ it('null context -> {}', async function () {
+ hook.hook_fn = async (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.callAllSerial(hookName, null);
+ });
+ it('context unmodified', async function () {
+ const wantContext = {};
+ hook.hook_fn = async (hn, ctx) => { assert.equal(ctx, wantContext); };
+ await hooks.callAllSerial(hookName, wantContext);
+ });
+ });
+ describe('result processing', function () {
+ it('no registered hooks (undefined) -> []', async function () {
+ delete plugins.hooks[hookName];
+ assert.deepEqual(await hooks.callAllSerial(hookName), []);
+ });
+ it('no registered hooks (empty list) -> []', async function () {
+ testHooks.length = 0;
+ assert.deepEqual(await hooks.callAllSerial(hookName), []);
+ });
+ it('flattens one level', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook([2]), makeHook([[3]]));
+ assert.deepEqual(await hooks.callAllSerial(hookName), [1, 2, [3]]);
+ });
+ it('filters out undefined', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook([2]), makeHook([[3]]), makeHook(Promise.resolve()));
+ assert.deepEqual(await hooks.callAllSerial(hookName), [2, [3]]);
+ });
+ it('preserves null', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(null), makeHook([2]), makeHook(Promise.resolve(null)));
+ assert.deepEqual(await hooks.callAllSerial(hookName), [null, 2, null]);
+ });
+ it('all undefined -> []', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook(Promise.resolve()));
+ assert.deepEqual(await hooks.callAllSerial(hookName), []);
+ });
+ });
});
-
- it('value can be passed via callback', async function () {
- const want = {};
- hook.hook_fn = (hn, ctx, cb) => { cb(want); };
- const got = await hooks.aCallFirst(hookName);
- assert.deepEqual(got, [want]);
- assert.equal(got[0], want); // Note: *NOT* deepEqual!
+ describe('hooks.aCallFirst', function () {
+ it('no registered hooks (undefined) -> []', async function () {
+ delete plugins.hooks.testHook;
+ assert.deepEqual(await hooks.aCallFirst(hookName), []);
+ });
+ it('no registered hooks (empty list) -> []', async function () {
+ testHooks.length = 0;
+ assert.deepEqual(await hooks.aCallFirst(hookName), []);
+ });
+ it('passes hook name => {}', async function () {
+ hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
+ await hooks.aCallFirst(hookName);
+ });
+ it('undefined context => {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.aCallFirst(hookName);
+ });
+ it('null context => {}', async function () {
+ hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
+ await hooks.aCallFirst(hookName, null);
+ });
+ it('context unmodified', async function () {
+ const wantContext = {};
+ hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
+ await hooks.aCallFirst(hookName, wantContext);
+ });
+ it('default predicate: predicate never satisfied -> calls all in order', async function () {
+ const gotCalls = [];
+ testHooks.length = 0;
+ for (let i = 0; i < 3; i++) {
+ const hook = makeHook();
+ hook.hook_fn = () => { gotCalls.push(i); };
+ testHooks.push(hook);
+ }
+ assert.deepEqual(await hooks.aCallFirst(hookName), []);
+ assert.deepEqual(gotCalls, [0, 1, 2]);
+ });
+ it('calls hook functions serially', async function () {
+ const gotCalls = [];
+ testHooks.length = 0;
+ for (let i = 0; i < 3; i++) {
+ const hook = makeHook();
+ hook.hook_fn = async () => {
+ gotCalls.push(i);
+ // Check gotCalls asynchronously to ensure that the next hook function does not start
+ // executing before this hook function has resolved.
+ return await new Promise((resolve) => {
+ setImmediate(() => {
+ assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
+ resolve();
+ });
+ });
+ };
+ testHooks.push(hook);
+ }
+ assert.deepEqual(await hooks.aCallFirst(hookName), []);
+ assert.deepEqual(gotCalls, [0, 1, 2]);
+ });
+ it('default predicate: stops when satisfied', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
+ assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
+ });
+ it('default predicate: skips values that do not satisfy (undefined)', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(), makeHook('val1'));
+ assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
+ });
+ it('default predicate: skips values that do not satisfy (empty list)', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook([]), makeHook('val1'));
+ assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
+ });
+ it('default predicate: null satisifes', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(null), makeHook('val1'));
+ assert.deepEqual(await hooks.aCallFirst(hookName), [null]);
+ });
+ it('custom predicate: called for each hook function', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(0), makeHook(1), makeHook(2));
+ let got = 0;
+ await hooks.aCallFirst(hookName, null, null, (val) => { ++got; return false; });
+ assert.equal(got, 3);
+ });
+ it('custom predicate: boolean false/true continues/stops iteration', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook(2), makeHook(3));
+ let nCall = 0;
+ const predicate = (val) => {
+ assert.deepEqual(val, [++nCall]);
+ return nCall === 2;
+ };
+ assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
+ assert.equal(nCall, 2);
+ });
+ it('custom predicate: non-boolean falsy/truthy continues/stops iteration', async function () {
+ testHooks.length = 0;
+ testHooks.push(makeHook(1), makeHook(2), makeHook(3));
+ let nCall = 0;
+ const predicate = (val) => {
+ assert.deepEqual(val, [++nCall]);
+ return nCall === 2 ? {} : null;
+ };
+ assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
+ assert.equal(nCall, 2);
+ });
+ it('custom predicate: array value passed unmodified to predicate', async function () {
+ const want = [0];
+ hook.hook_fn = () => want;
+ const predicate = (got) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
+ await hooks.aCallFirst(hookName, null, null, predicate);
+ });
+ it('custom predicate: normalized value passed to predicate (undefined)', async function () {
+ const predicate = (got) => { assert.deepEqual(got, []); };
+ await hooks.aCallFirst(hookName, null, null, predicate);
+ });
+ it('custom predicate: normalized value passed to predicate (null)', async function () {
+ hook.hook_fn = () => null;
+ const predicate = (got) => { assert.deepEqual(got, [null]); };
+ await hooks.aCallFirst(hookName, null, null, predicate);
+ });
+ it('non-empty arrays are returned unmodified', async function () {
+ const want = ['val1'];
+ testHooks.length = 0;
+ testHooks.push(makeHook(want), makeHook(['val2']));
+ assert.equal(await hooks.aCallFirst(hookName), want); // Note: *NOT* deepEqual!
+ });
+ it('value can be passed via callback', async function () {
+ const want = {};
+ hook.hook_fn = (hn, ctx, cb) => { cb(want); };
+ const got = await hooks.aCallFirst(hookName);
+ assert.deepEqual(got, [want]);
+ assert.equal(got[0], want); // Note: *NOT* deepEqual!
+ });
});
- });
});
diff --git a/src/tests/backend/specs/messages.js b/src/tests/backend/specs/messages.js
index bccb2584dc0..e79a7e3d08b 100644
--- a/src/tests/backend/specs/messages.js
+++ b/src/tests/backend/specs/messages.js
@@ -1,171 +1,159 @@
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as padManager from "../../../node/db/PadManager.js";
+import * as plugins from "../../../static/js/pluginfw/plugin_defs.js";
+import * as readOnlyManager from "../../../node/db/ReadOnlyManager.js";
'use strict';
-
-const assert = require('assert').strict;
-const common = require('../common');
-const padManager = require('../../../node/db/PadManager');
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const readOnlyManager = require('../../../node/db/ReadOnlyManager');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- let agent;
- let pad;
- let padId;
- let roPadId;
- let rev;
- let socket;
- let roSocket;
- const backups = {};
-
- before(async function () {
- agent = await common.init();
- });
-
- beforeEach(async function () {
- backups.hooks = {handleMessageSecurity: plugins.hooks.handleMessageSecurity};
- plugins.hooks.handleMessageSecurity = [];
- padId = common.randomString();
- assert(!await padManager.doesPadExist(padId));
- pad = await padManager.getPad(padId, 'dummy text\n');
- await pad.setText('\n'); // Make sure the pad is created.
- assert.equal(pad.text(), '\n');
- let res = await agent.get(`/p/${padId}`).expect(200);
- socket = await common.connect(res);
- const {type, data: clientVars} = await common.handshake(socket, padId);
- assert.equal(type, 'CLIENT_VARS');
- rev = clientVars.collab_client_vars.rev;
-
- roPadId = await readOnlyManager.getReadOnlyId(padId);
- res = await agent.get(`/p/${roPadId}`).expect(200);
- roSocket = await common.connect(res);
- await common.handshake(roSocket, roPadId);
- });
-
- afterEach(async function () {
- Object.assign(plugins.hooks, backups.hooks);
- if (socket != null) socket.close();
- socket = null;
- if (roSocket != null) roSocket.close();
- roSocket = null;
- if (pad != null) await pad.remove();
- pad = null;
- });
-
- describe('CHANGESET_REQ', function () {
- it('users are unable to read changesets from other pads', async function () {
- const otherPadId = `${padId}other`;
- assert(!await padManager.doesPadExist(otherPadId));
- const otherPad = await padManager.getPad(otherPadId, 'other text\n');
- try {
- await otherPad.setText('other text\n');
- const resP = common.waitForSocketEvent(roSocket, 'message');
- await common.sendMessage(roSocket, {
- component: 'pad',
- padId: otherPadId, // The server should ignore this.
- type: 'CHANGESET_REQ',
- data: {
- granularity: 1,
- start: 0,
- requestID: 'requestId',
- },
- });
- const res = await resP;
- assert.equal(res.type, 'CHANGESET_REQ');
- assert.equal(res.data.requestID, 'requestId');
- // Should match padId's text, not otherPadId's text.
- assert.match(res.data.forwardsChangesets[0], /^[^$]*\$dummy text\n/);
- } finally {
- await otherPad.remove();
- }
- });
- });
-
- describe('USER_CHANGES', function () {
- const sendUserChanges =
- async (socket, cs) => await common.sendUserChanges(socket, {baseRev: rev, changeset: cs});
- const assertAccepted = async (socket, wantRev) => {
- await common.waitForAcceptCommit(socket, wantRev);
- rev = wantRev;
- };
- const assertRejected = async (socket) => {
- const msg = await common.waitForSocketEvent(socket, 'message');
- assert.deepEqual(msg, {disconnect: 'badChangeset'});
- };
-
- it('changes are applied', async function () {
- await Promise.all([
- assertAccepted(socket, rev + 1),
- sendUserChanges(socket, 'Z:1>5+5$hello'),
- ]);
- assert.equal(pad.text(), 'hello\n');
- });
-
- it('bad changeset is rejected', async function () {
- await Promise.all([
- assertRejected(socket),
- sendUserChanges(socket, 'this is not a valid changeset'),
- ]);
+ let agent;
+ let pad;
+ let padId;
+ let roPadId;
+ let rev;
+ let socket;
+ let roSocket;
+ const backups = {};
+ before(async function () {
+ agent = await common.init();
});
-
- it('retransmission is accepted, has no effect', async function () {
- const cs = 'Z:1>5+5$hello';
- await Promise.all([
- assertAccepted(socket, rev + 1),
- sendUserChanges(socket, cs),
- ]);
- --rev;
- await Promise.all([
- assertAccepted(socket, rev + 1),
- sendUserChanges(socket, cs),
- ]);
- assert.equal(pad.text(), 'hello\n');
+ beforeEach(async function () {
+ backups.hooks = { handleMessageSecurity: plugins.hooks.handleMessageSecurity };
+ plugins.hooks.handleMessageSecurity = [];
+ padId = common.randomString();
+ assert(!await padManager.doesPadExist(padId));
+ pad = await padManager.getPad(padId, 'dummy text\n');
+ await pad.setText('\n'); // Make sure the pad is created.
+ assert.equal(pad.text(), '\n');
+ let res = await agent.get(`/p/${padId}`).expect(200);
+ socket = await common.connect(res);
+ const { type, data: clientVars } = await common.handshake(socket, padId);
+ assert.equal(type, 'CLIENT_VARS');
+ rev = clientVars.collab_client_vars.rev;
+ roPadId = await readOnlyManager.getReadOnlyId(padId);
+ res = await agent.get(`/p/${roPadId}`).expect(200);
+ roSocket = await common.connect(res);
+ await common.handshake(roSocket, roPadId);
});
-
- it('identity changeset is accepted, has no effect', async function () {
- await Promise.all([
- assertAccepted(socket, rev + 1),
- sendUserChanges(socket, 'Z:1>5+5$hello'),
- ]);
- await Promise.all([
- assertAccepted(socket, rev),
- sendUserChanges(socket, 'Z:6>0$'),
- ]);
- assert.equal(pad.text(), 'hello\n');
+ afterEach(async function () {
+ Object.assign(plugins.hooks, backups.hooks);
+ if (socket != null)
+ socket.close();
+ socket = null;
+ if (roSocket != null)
+ roSocket.close();
+ roSocket = null;
+ if (pad != null)
+ await pad.remove();
+ pad = null;
});
-
- it('non-identity changeset with no net change is accepted, has no effect', async function () {
- await Promise.all([
- assertAccepted(socket, rev + 1),
- sendUserChanges(socket, 'Z:1>5+5$hello'),
- ]);
- await Promise.all([
- assertAccepted(socket, rev),
- sendUserChanges(socket, 'Z:6>0-5+5$hello'),
- ]);
- assert.equal(pad.text(), 'hello\n');
+ describe('CHANGESET_REQ', function () {
+ it('users are unable to read changesets from other pads', async function () {
+ const otherPadId = `${padId}other`;
+ assert(!await padManager.doesPadExist(otherPadId));
+ const otherPad = await padManager.getPad(otherPadId, 'other text\n');
+ try {
+ await otherPad.setText('other text\n');
+ const resP = common.waitForSocketEvent(roSocket, 'message');
+ await common.sendMessage(roSocket, {
+ component: 'pad',
+ padId: otherPadId,
+ type: 'CHANGESET_REQ',
+ data: {
+ granularity: 1,
+ start: 0,
+ requestID: 'requestId',
+ },
+ });
+ const res = await resP;
+ assert.equal(res.type, 'CHANGESET_REQ');
+ assert.equal(res.data.requestID, 'requestId');
+ // Should match padId's text, not otherPadId's text.
+ assert.match(res.data.forwardsChangesets[0], /^[^$]*\$dummy text\n/);
+ }
+ finally {
+ await otherPad.remove();
+ }
+ });
});
-
- it('handleMessageSecurity can grant one-time write access', async function () {
- const cs = 'Z:1>5+5$hello';
- const errRegEx = /write attempt on read-only pad/;
- // First try to send a change and verify that it was dropped.
- await assert.rejects(sendUserChanges(roSocket, cs), errRegEx);
- // sendUserChanges() waits for message ack, so if the message was accepted then head should
- // have already incremented by the time we get here.
- assert.equal(pad.head, rev); // Not incremented.
-
- // Now allow the change.
- plugins.hooks.handleMessageSecurity.push({hook_fn: () => 'permitOnce'});
- await Promise.all([
- assertAccepted(roSocket, rev + 1),
- sendUserChanges(roSocket, cs),
- ]);
- assert.equal(pad.text(), 'hello\n');
-
- // The next change should be dropped.
- plugins.hooks.handleMessageSecurity = [];
- await assert.rejects(sendUserChanges(roSocket, 'Z:6>6=5+6$ world'), errRegEx);
- assert.equal(pad.head, rev); // Not incremented.
- assert.equal(pad.text(), 'hello\n');
+ describe('USER_CHANGES', function () {
+ const sendUserChanges = async (socket, cs) => await common.sendUserChanges(socket, { baseRev: rev, changeset: cs });
+ const assertAccepted = async (socket, wantRev) => {
+ await common.waitForAcceptCommit(socket, wantRev);
+ rev = wantRev;
+ };
+ const assertRejected = async (socket) => {
+ const msg = await common.waitForSocketEvent(socket, 'message');
+ assert.deepEqual(msg, { disconnect: 'badChangeset' });
+ };
+ it('changes are applied', async function () {
+ await Promise.all([
+ assertAccepted(socket, rev + 1),
+ sendUserChanges(socket, 'Z:1>5+5$hello'),
+ ]);
+ assert.equal(pad.text(), 'hello\n');
+ });
+ it('bad changeset is rejected', async function () {
+ await Promise.all([
+ assertRejected(socket),
+ sendUserChanges(socket, 'this is not a valid changeset'),
+ ]);
+ });
+ it('retransmission is accepted, has no effect', async function () {
+ const cs = 'Z:1>5+5$hello';
+ await Promise.all([
+ assertAccepted(socket, rev + 1),
+ sendUserChanges(socket, cs),
+ ]);
+ --rev;
+ await Promise.all([
+ assertAccepted(socket, rev + 1),
+ sendUserChanges(socket, cs),
+ ]);
+ assert.equal(pad.text(), 'hello\n');
+ });
+ it('identity changeset is accepted, has no effect', async function () {
+ await Promise.all([
+ assertAccepted(socket, rev + 1),
+ sendUserChanges(socket, 'Z:1>5+5$hello'),
+ ]);
+ await Promise.all([
+ assertAccepted(socket, rev),
+ sendUserChanges(socket, 'Z:6>0$'),
+ ]);
+ assert.equal(pad.text(), 'hello\n');
+ });
+ it('non-identity changeset with no net change is accepted, has no effect', async function () {
+ await Promise.all([
+ assertAccepted(socket, rev + 1),
+ sendUserChanges(socket, 'Z:1>5+5$hello'),
+ ]);
+ await Promise.all([
+ assertAccepted(socket, rev),
+ sendUserChanges(socket, 'Z:6>0-5+5$hello'),
+ ]);
+ assert.equal(pad.text(), 'hello\n');
+ });
+ it('handleMessageSecurity can grant one-time write access', async function () {
+ const cs = 'Z:1>5+5$hello';
+ const errRegEx = /write attempt on read-only pad/;
+ // First try to send a change and verify that it was dropped.
+ await assert.rejects(sendUserChanges(roSocket, cs), errRegEx);
+ // sendUserChanges() waits for message ack, so if the message was accepted then head should
+ // have already incremented by the time we get here.
+ assert.equal(pad.head, rev); // Not incremented.
+ // Now allow the change.
+ plugins.hooks.handleMessageSecurity.push({ hook_fn: () => 'permitOnce' });
+ await Promise.all([
+ assertAccepted(roSocket, rev + 1),
+ sendUserChanges(roSocket, cs),
+ ]);
+ assert.equal(pad.text(), 'hello\n');
+ // The next change should be dropped.
+ plugins.hooks.handleMessageSecurity = [];
+ await assert.rejects(sendUserChanges(roSocket, 'Z:6>6=5+6$ world'), errRegEx);
+ assert.equal(pad.head, rev); // Not incremented.
+ assert.equal(pad.text(), 'hello\n');
+ });
});
- });
});
diff --git a/src/tests/backend/specs/pad_utils.js b/src/tests/backend/specs/pad_utils.js
index b4e815187b6..b2adae91c8e 100644
--- a/src/tests/backend/specs/pad_utils.js
+++ b/src/tests/backend/specs/pad_utils.js
@@ -1,43 +1,38 @@
+import assert$0 from "assert";
+import { padutils } from "../../../static/js/pad_utils.js";
'use strict';
-
-const assert = require('assert').strict;
-const {padutils} = require('../../../static/js/pad_utils');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- describe('warnDeprecated', function () {
- const {warnDeprecated} = padutils;
- const backups = {};
-
- before(async function () {
- backups.logger = warnDeprecated.logger;
+ describe('warnDeprecated', function () {
+ const { warnDeprecated } = padutils;
+ const backups = {};
+ before(async function () {
+ backups.logger = warnDeprecated.logger;
+ });
+ afterEach(async function () {
+ warnDeprecated.logger = backups.logger;
+ delete warnDeprecated._rl; // Reset internal rate limiter state.
+ });
+ it('includes the stack', async function () {
+ let got;
+ warnDeprecated.logger = { warn: (stack) => got = stack };
+ warnDeprecated();
+ assert(got.includes(__filename));
+ });
+ it('rate limited', async function () {
+ let got = 0;
+ warnDeprecated.logger = { warn: () => ++got };
+ warnDeprecated(); // Initialize internal rate limiter state.
+ const { period } = warnDeprecated._rl;
+ got = 0;
+ const testCases = [[0, 1], [0, 1], [period - 1, 1], [period, 2]];
+ for (const [now, want] of testCases) { // In a loop so that the stack trace is the same.
+ warnDeprecated._rl.now = () => now;
+ warnDeprecated();
+ assert.equal(got, want);
+ }
+ warnDeprecated(); // Should have a different stack trace.
+ assert.equal(got, testCases[testCases.length - 1][1] + 1);
+ });
});
-
- afterEach(async function () {
- warnDeprecated.logger = backups.logger;
- delete warnDeprecated._rl; // Reset internal rate limiter state.
- });
-
- it('includes the stack', async function () {
- let got;
- warnDeprecated.logger = {warn: (stack) => got = stack};
- warnDeprecated();
- assert(got.includes(__filename));
- });
-
- it('rate limited', async function () {
- let got = 0;
- warnDeprecated.logger = {warn: () => ++got};
- warnDeprecated(); // Initialize internal rate limiter state.
- const {period} = warnDeprecated._rl;
- got = 0;
- const testCases = [[0, 1], [0, 1], [period - 1, 1], [period, 2]];
- for (const [now, want] of testCases) { // In a loop so that the stack trace is the same.
- warnDeprecated._rl.now = () => now;
- warnDeprecated();
- assert.equal(got, want);
- }
- warnDeprecated(); // Should have a different stack trace.
- assert.equal(got, testCases[testCases.length - 1][1] + 1);
- });
- });
});
diff --git a/src/tests/backend/specs/pads-with-spaces.js b/src/tests/backend/specs/pads-with-spaces.js
index 0db99865b80..f855340ff02 100644
--- a/src/tests/backend/specs/pads-with-spaces.js
+++ b/src/tests/backend/specs/pads-with-spaces.js
@@ -1,24 +1,20 @@
+import * as common from "../common.js";
+import assertLegacy from "../assert-legacy.js";
'use strict';
-
-const common = require('../common');
-const assert = require('../assert-legacy').strict;
-
+const assert = assertLegacy.strict;
let agent;
-
describe(__filename, function () {
- before(async function () {
- agent = await common.init();
- });
-
- it('supports pads with spaces, regression test for #4883', async function () {
- await agent.get('/p/pads with spaces')
- .expect(302)
- .expect('location', 'pads_with_spaces');
- });
-
- it('supports pads with spaces and query, regression test for #4883', async function () {
- await agent.get('/p/pads with spaces?showChat=true&noColors=false')
- .expect(302)
- .expect('location', 'pads_with_spaces?showChat=true&noColors=false');
- });
+ before(async function () {
+ agent = await common.init();
+ });
+ it('supports pads with spaces, regression test for #4883', async function () {
+ await agent.get('/p/pads with spaces')
+ .expect(302)
+ .expect('location', 'pads_with_spaces');
+ });
+ it('supports pads with spaces and query, regression test for #4883', async function () {
+ await agent.get('/p/pads with spaces?showChat=true&noColors=false')
+ .expect(302)
+ .expect('location', 'pads_with_spaces?showChat=true&noColors=false');
+ });
});
diff --git a/src/tests/backend/specs/promises.js b/src/tests/backend/specs/promises.js
index ad0c1ad9269..45fb135cb9b 100644
--- a/src/tests/backend/specs/promises.js
+++ b/src/tests/backend/specs/promises.js
@@ -1,85 +1,76 @@
-const assert = require('assert').strict;
-const promises = require('../../../node/utils/promises');
-
+import assert$0 from "assert";
+import * as promises from "../../../node/utils/promises.js";
+const assert = assert$0.strict;
describe(__filename, function () {
- describe('promises.timesLimit', function () {
- let wantIndex = 0;
- const testPromises = [];
- const makePromise = (index) => {
- // Make sure index increases by one each time.
- assert.equal(index, wantIndex++);
- // Save the resolve callback (so the test can trigger resolution)
- // and the promise itself (to wait for resolve to take effect).
- const p = {};
- const promise = new Promise((resolve) => {
- p.resolve = resolve;
- });
- p.promise = promise;
- testPromises.push(p);
- return p.promise;
- };
-
- const total = 11;
- const concurrency = 7;
- const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
-
- it('honors concurrency', async function () {
- assert.equal(wantIndex, concurrency);
+ describe('promises.timesLimit', function () {
+ let wantIndex = 0;
+ const testPromises = [];
+ const makePromise = (index) => {
+ // Make sure index increases by one each time.
+ assert.equal(index, wantIndex++);
+ // Save the resolve callback (so the test can trigger resolution)
+ // and the promise itself (to wait for resolve to take effect).
+ const p = {};
+ const promise = new Promise((resolve) => {
+ p.resolve = resolve;
+ });
+ p.promise = promise;
+ testPromises.push(p);
+ return p.promise;
+ };
+ const total = 11;
+ const concurrency = 7;
+ const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
+ it('honors concurrency', async function () {
+ assert.equal(wantIndex, concurrency);
+ });
+ it('creates another when one completes', async function () {
+ const { promise, resolve } = testPromises.shift();
+ resolve();
+ await promise;
+ assert.equal(wantIndex, concurrency + 1);
+ });
+ it('creates the expected total number of promises', async function () {
+ while (testPromises.length > 0) {
+ // Resolve them in random order to ensure that the resolution order doesn't matter.
+ const i = Math.floor(Math.random() * Math.floor(testPromises.length));
+ const { promise, resolve } = testPromises.splice(i, 1)[0];
+ resolve();
+ await promise;
+ }
+ assert.equal(wantIndex, total);
+ });
+ it('resolves', async function () {
+ await timesLimitPromise;
+ });
+ it('does not create too many promises if total < concurrency', async function () {
+ wantIndex = 0;
+ assert.equal(testPromises.length, 0);
+ const total = 7;
+ const concurrency = 11;
+ const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
+ while (testPromises.length > 0) {
+ const { promise, resolve } = testPromises.pop();
+ resolve();
+ await promise;
+ }
+ await timesLimitPromise;
+ assert.equal(wantIndex, total);
+ });
+ it('accepts total === 0, concurrency > 0', async function () {
+ wantIndex = 0;
+ assert.equal(testPromises.length, 0);
+ await promises.timesLimit(0, concurrency, makePromise);
+ assert.equal(wantIndex, 0);
+ });
+ it('accepts total === 0, concurrency === 0', async function () {
+ wantIndex = 0;
+ assert.equal(testPromises.length, 0);
+ await promises.timesLimit(0, 0, makePromise);
+ assert.equal(wantIndex, 0);
+ });
+ it('rejects total > 0, concurrency === 0', async function () {
+ await assert.rejects(promises.timesLimit(total, 0, makePromise), RangeError);
+ });
});
-
- it('creates another when one completes', async function () {
- const {promise, resolve} = testPromises.shift();
- resolve();
- await promise;
- assert.equal(wantIndex, concurrency + 1);
- });
-
- it('creates the expected total number of promises', async function () {
- while (testPromises.length > 0) {
- // Resolve them in random order to ensure that the resolution order doesn't matter.
- const i = Math.floor(Math.random() * Math.floor(testPromises.length));
- const {promise, resolve} = testPromises.splice(i, 1)[0];
- resolve();
- await promise;
- }
- assert.equal(wantIndex, total);
- });
-
- it('resolves', async function () {
- await timesLimitPromise;
- });
-
- it('does not create too many promises if total < concurrency', async function () {
- wantIndex = 0;
- assert.equal(testPromises.length, 0);
- const total = 7;
- const concurrency = 11;
- const timesLimitPromise = promises.timesLimit(total, concurrency, makePromise);
- while (testPromises.length > 0) {
- const {promise, resolve} = testPromises.pop();
- resolve();
- await promise;
- }
- await timesLimitPromise;
- assert.equal(wantIndex, total);
- });
-
- it('accepts total === 0, concurrency > 0', async function () {
- wantIndex = 0;
- assert.equal(testPromises.length, 0);
- await promises.timesLimit(0, concurrency, makePromise);
- assert.equal(wantIndex, 0);
- });
-
- it('accepts total === 0, concurrency === 0', async function () {
- wantIndex = 0;
- assert.equal(testPromises.length, 0);
- await promises.timesLimit(0, 0, makePromise);
- assert.equal(wantIndex, 0);
- });
-
- it('rejects total > 0, concurrency === 0', async function () {
- await assert.rejects(promises.timesLimit(total, 0, makePromise), RangeError);
- });
- });
});
diff --git a/src/tests/backend/specs/regression-db.js b/src/tests/backend/specs/regression-db.js
index 388b8346ab4..aa81bace2db 100644
--- a/src/tests/backend/specs/regression-db.js
+++ b/src/tests/backend/specs/regression-db.js
@@ -1,30 +1,25 @@
+import * as AuthorManager from "../../../node/db/AuthorManager.js";
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as db from "../../../node/db/DB.js";
'use strict';
-
-const AuthorManager = require('../../../node/db/AuthorManager');
-const assert = require('assert').strict;
-const common = require('../common');
-const db = require('../../../node/db/DB');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- let setBackup;
-
- before(async function () {
- await common.init();
- setBackup = db.set;
-
- db.set = async (...args) => {
- // delay db.set
- await new Promise((resolve) => { setTimeout(() => resolve(), 500); });
- return await setBackup.call(db, ...args);
- };
- });
-
- after(async function () {
- db.set = setBackup;
- });
-
- it('regression test for missing await in createAuthor (#5000)', async function () {
- const {authorID} = await AuthorManager.createAuthor(); // Should block until db.set() finishes.
- assert(await AuthorManager.doesAuthorExist(authorID));
- });
+ let setBackup;
+ before(async function () {
+ await common.init();
+ setBackup = db.set;
+ db.set = async (...args) => {
+ // delay db.set
+ await new Promise((resolve) => { setTimeout(() => resolve(), 500); });
+ return await setBackup.call(db, ...args);
+ };
+ });
+ after(async function () {
+ db.set = setBackup;
+ });
+ it('regression test for missing await in createAuthor (#5000)', async function () {
+ const { authorID } = await AuthorManager.createAuthor(); // Should block until db.set() finishes.
+ assert(await AuthorManager.doesAuthorExist(authorID));
+ });
});
diff --git a/src/tests/backend/specs/sanitizePathname.js b/src/tests/backend/specs/sanitizePathname.js
index 767221920dd..c99f3aafec6 100644
--- a/src/tests/backend/specs/sanitizePathname.js
+++ b/src/tests/backend/specs/sanitizePathname.js
@@ -1,96 +1,93 @@
+import assert$0 from "assert";
+import path from "path";
+import sanitizePathname from "../../../node/utils/sanitizePathname.js";
'use strict';
-
-const assert = require('assert').strict;
-const path = require('path');
-const sanitizePathname = require('../../../node/utils/sanitizePathname');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- describe('absolute paths rejected', function () {
- const testCases = [
- ['posix', '/'],
- ['posix', '/foo'],
- ['win32', '/'],
- ['win32', '\\'],
- ['win32', 'C:/foo'],
- ['win32', 'C:\\foo'],
- ['win32', 'c:/foo'],
- ['win32', 'c:\\foo'],
- ['win32', '/foo'],
- ['win32', '\\foo'],
- ];
- for (const [platform, p] of testCases) {
- it(`${platform} ${p}`, async function () {
- assert.throws(() => sanitizePathname(p, path[platform]), {message: /absolute path/});
- });
- }
- });
- describe('directory traversal rejected', function () {
- const testCases = [
- ['posix', '..'],
- ['posix', '../'],
- ['posix', '../foo'],
- ['posix', 'foo/../..'],
- ['win32', '..'],
- ['win32', '../'],
- ['win32', '..\\'],
- ['win32', '../foo'],
- ['win32', '..\\foo'],
- ['win32', 'foo/../..'],
- ['win32', 'foo\\..\\..'],
- ];
- for (const [platform, p] of testCases) {
- it(`${platform} ${p}`, async function () {
- assert.throws(() => sanitizePathname(p, path[platform]), {message: /travers/});
- });
- }
- });
-
- describe('accepted paths', function () {
- const testCases = [
- ['posix', '', '.'],
- ['posix', '.'],
- ['posix', './'],
- ['posix', 'foo'],
- ['posix', 'foo/'],
- ['posix', 'foo/bar/..', 'foo'],
- ['posix', 'foo/bar/../', 'foo/'],
- ['posix', './foo', 'foo'],
- ['posix', 'foo/bar'],
- ['posix', 'foo\\bar'],
- ['posix', '\\foo'],
- ['posix', '..\\foo'],
- ['posix', 'foo/../bar', 'bar'],
- ['posix', 'C:/foo'],
- ['posix', 'C:\\foo'],
- ['win32', '', '.'],
- ['win32', '.'],
- ['win32', './'],
- ['win32', '.\\', './'],
- ['win32', 'foo'],
- ['win32', 'foo/'],
- ['win32', 'foo\\', 'foo/'],
- ['win32', 'foo/bar/..', 'foo'],
- ['win32', 'foo\\bar\\..', 'foo'],
- ['win32', 'foo/bar/../', 'foo/'],
- ['win32', 'foo\\bar\\..\\', 'foo/'],
- ['win32', './foo', 'foo'],
- ['win32', '.\\foo', 'foo'],
- ['win32', 'foo/bar'],
- ['win32', 'foo\\bar', 'foo/bar'],
- ['win32', 'foo/../bar', 'bar'],
- ['win32', 'foo\\..\\bar', 'bar'],
- ['win32', 'foo/..\\bar', 'bar'],
- ['win32', 'foo\\../bar', 'bar'],
- ];
- for (const [platform, p, tcWant] of testCases) {
- const want = tcWant == null ? p : tcWant;
- it(`${platform} ${p || ''} -> ${want}`, async function () {
- assert.equal(sanitizePathname(p, path[platform]), want);
- });
- }
- });
-
- it('default path API', async function () {
- assert.equal(sanitizePathname('foo'), 'foo');
- });
+ describe('absolute paths rejected', function () {
+ const testCases = [
+ ['posix', '/'],
+ ['posix', '/foo'],
+ ['win32', '/'],
+ ['win32', '\\'],
+ ['win32', 'C:/foo'],
+ ['win32', 'C:\\foo'],
+ ['win32', 'c:/foo'],
+ ['win32', 'c:\\foo'],
+ ['win32', '/foo'],
+ ['win32', '\\foo'],
+ ];
+ for (const [platform, p] of testCases) {
+ it(`${platform} ${p}`, async function () {
+ assert.throws(() => sanitizePathname(p, path[platform]), { message: /absolute path/ });
+ });
+ }
+ });
+ describe('directory traversal rejected', function () {
+ const testCases = [
+ ['posix', '..'],
+ ['posix', '../'],
+ ['posix', '../foo'],
+ ['posix', 'foo/../..'],
+ ['win32', '..'],
+ ['win32', '../'],
+ ['win32', '..\\'],
+ ['win32', '../foo'],
+ ['win32', '..\\foo'],
+ ['win32', 'foo/../..'],
+ ['win32', 'foo\\..\\..'],
+ ];
+ for (const [platform, p] of testCases) {
+ it(`${platform} ${p}`, async function () {
+ assert.throws(() => sanitizePathname(p, path[platform]), { message: /travers/ });
+ });
+ }
+ });
+ describe('accepted paths', function () {
+ const testCases = [
+ ['posix', '', '.'],
+ ['posix', '.'],
+ ['posix', './'],
+ ['posix', 'foo'],
+ ['posix', 'foo/'],
+ ['posix', 'foo/bar/..', 'foo'],
+ ['posix', 'foo/bar/../', 'foo/'],
+ ['posix', './foo', 'foo'],
+ ['posix', 'foo/bar'],
+ ['posix', 'foo\\bar'],
+ ['posix', '\\foo'],
+ ['posix', '..\\foo'],
+ ['posix', 'foo/../bar', 'bar'],
+ ['posix', 'C:/foo'],
+ ['posix', 'C:\\foo'],
+ ['win32', '', '.'],
+ ['win32', '.'],
+ ['win32', './'],
+ ['win32', '.\\', './'],
+ ['win32', 'foo'],
+ ['win32', 'foo/'],
+ ['win32', 'foo\\', 'foo/'],
+ ['win32', 'foo/bar/..', 'foo'],
+ ['win32', 'foo\\bar\\..', 'foo'],
+ ['win32', 'foo/bar/../', 'foo/'],
+ ['win32', 'foo\\bar\\..\\', 'foo/'],
+ ['win32', './foo', 'foo'],
+ ['win32', '.\\foo', 'foo'],
+ ['win32', 'foo/bar'],
+ ['win32', 'foo\\bar', 'foo/bar'],
+ ['win32', 'foo/../bar', 'bar'],
+ ['win32', 'foo\\..\\bar', 'bar'],
+ ['win32', 'foo/..\\bar', 'bar'],
+ ['win32', 'foo\\../bar', 'bar'],
+ ];
+ for (const [platform, p, tcWant] of testCases) {
+ const want = tcWant == null ? p : tcWant;
+ it(`${platform} ${p || ''} -> ${want}`, async function () {
+ assert.equal(sanitizePathname(p, path[platform]), want);
+ });
+ }
+ });
+ it('default path API', async function () {
+ assert.equal(sanitizePathname('foo'), 'foo');
+ });
});
diff --git a/src/tests/backend/specs/settings.js b/src/tests/backend/specs/settings.js
index e737f4f3445..6de6fae4110 100644
--- a/src/tests/backend/specs/settings.js
+++ b/src/tests/backend/specs/settings.js
@@ -1,61 +1,60 @@
+import assert$0 from "assert";
+import { exportedForTestingOnly } from "../../../node/utils/Settings.js";
+import path from "path";
+import process from "process";
'use strict';
-
-const assert = require('assert').strict;
-const {parseSettings} = require('../../../node/utils/Settings').exportedForTestingOnly;
-const path = require('path');
-const process = require('process');
-
+const assert = assert$0.strict;
+const { parseSettings } = { exportedForTestingOnly }.exportedForTestingOnly;
describe(__filename, function () {
- describe('parseSettings', function () {
- let settings;
- const envVarSubstTestCases = [
- {name: 'true', val: 'true', var: 'SET_VAR_TRUE', want: true},
- {name: 'false', val: 'false', var: 'SET_VAR_FALSE', want: false},
- {name: 'null', val: 'null', var: 'SET_VAR_NULL', want: null},
- {name: 'undefined', val: 'undefined', var: 'SET_VAR_UNDEFINED', want: undefined},
- {name: 'number', val: '123', var: 'SET_VAR_NUMBER', want: 123},
- {name: 'string', val: 'foo', var: 'SET_VAR_STRING', want: 'foo'},
- {name: 'empty string', val: '', var: 'SET_VAR_EMPTY_STRING', want: ''},
- ];
-
- before(async function () {
- for (const tc of envVarSubstTestCases) process.env[tc.var] = tc.val;
- delete process.env.UNSET_VAR;
- settings = parseSettings(path.join(__dirname, 'settings.json'), true);
- assert(settings != null);
- });
-
- describe('environment variable substitution', function () {
- describe('set', function () {
- for (const tc of envVarSubstTestCases) {
- it(tc.name, async function () {
- const obj = settings['environment variable substitution'].set;
- if (tc.name === 'undefined') {
- assert(!(tc.name in obj));
- } else {
- assert.equal(obj[tc.name], tc.want);
- }
- });
- }
- });
-
- describe('unset', function () {
- it('no default', async function () {
- const obj = settings['environment variable substitution'].unset;
- assert.equal(obj['no default'], null);
+ describe('parseSettings', function () {
+ let settings;
+ const envVarSubstTestCases = [
+ { name: 'true', val: 'true', var: 'SET_VAR_TRUE', want: true },
+ { name: 'false', val: 'false', var: 'SET_VAR_FALSE', want: false },
+ { name: 'null', val: 'null', var: 'SET_VAR_NULL', want: null },
+ { name: 'undefined', val: 'undefined', var: 'SET_VAR_UNDEFINED', want: undefined },
+ { name: 'number', val: '123', var: 'SET_VAR_NUMBER', want: 123 },
+ { name: 'string', val: 'foo', var: 'SET_VAR_STRING', want: 'foo' },
+ { name: 'empty string', val: '', var: 'SET_VAR_EMPTY_STRING', want: '' },
+ ];
+ before(async function () {
+ for (const tc of envVarSubstTestCases)
+ process.env[tc.var] = tc.val;
+ delete process.env.UNSET_VAR;
+ settings = parseSettings(path.join(__dirname, 'settings.json'), true);
+ assert(settings != null);
+ });
+ describe('environment variable substitution', function () {
+ describe('set', function () {
+ for (const tc of envVarSubstTestCases) {
+ it(tc.name, async function () {
+ const obj = settings['environment variable substitution'].set;
+ if (tc.name === 'undefined') {
+ assert(!(tc.name in obj));
+ }
+ else {
+ assert.equal(obj[tc.name], tc.want);
+ }
+ });
+ }
+ });
+ describe('unset', function () {
+ it('no default', async function () {
+ const obj = settings['environment variable substitution'].unset;
+ assert.equal(obj['no default'], null);
+ });
+ for (const tc of envVarSubstTestCases) {
+ it(tc.name, async function () {
+ const obj = settings['environment variable substitution'].unset;
+ if (tc.name === 'undefined') {
+ assert(!(tc.name in obj));
+ }
+ else {
+ assert.equal(obj[tc.name], tc.want);
+ }
+ });
+ }
+ });
});
-
- for (const tc of envVarSubstTestCases) {
- it(tc.name, async function () {
- const obj = settings['environment variable substitution'].unset;
- if (tc.name === 'undefined') {
- assert(!(tc.name in obj));
- } else {
- assert.equal(obj[tc.name], tc.want);
- }
- });
- }
- });
});
- });
});
diff --git a/src/tests/backend/specs/socketio.js b/src/tests/backend/specs/socketio.js
index 15f56177499..7f95d648a61 100644
--- a/src/tests/backend/specs/socketio.js
+++ b/src/tests/backend/specs/socketio.js
@@ -1,426 +1,405 @@
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as padManager from "../../../node/db/PadManager.js";
+import * as plugins from "../../../static/js/pluginfw/plugin_defs.js";
+import * as readOnlyManager from "../../../node/db/ReadOnlyManager.js";
+import * as settings from "../../../node/utils/Settings.js";
+import * as socketIoRouter from "../../../node/handler/SocketIORouter.js";
'use strict';
-
-const assert = require('assert').strict;
-const common = require('../common');
-const padManager = require('../../../node/db/PadManager');
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const readOnlyManager = require('../../../node/db/ReadOnlyManager');
-const settings = require('../../../node/utils/Settings');
-const socketIoRouter = require('../../../node/handler/SocketIORouter');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- this.timeout(30000);
- let agent;
- let authorize;
- const backups = {};
- const cleanUpPads = async () => {
- const padIds = ['pad', 'other-pad', 'päd'];
- await Promise.all(padIds.map(async (padId) => {
- if (await padManager.doesPadExist(padId)) {
- const pad = await padManager.getPad(padId);
- await pad.remove();
- }
- }));
- };
- let socket;
-
- before(async function () { agent = await common.init(); });
- beforeEach(async function () {
- backups.hooks = {};
- for (const hookName of ['preAuthorize', 'authenticate', 'authorize']) {
- backups.hooks[hookName] = plugins.hooks[hookName];
- plugins.hooks[hookName] = [];
- }
- backups.settings = {};
- for (const setting of ['editOnly', 'requireAuthentication', 'requireAuthorization', 'users']) {
- backups.settings[setting] = settings[setting];
- }
- settings.editOnly = false;
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- settings.users = {
- admin: {password: 'admin-password', is_admin: true},
- user: {password: 'user-password'},
+ this.timeout(30000);
+ let agent;
+ let authorize;
+ const backups = {};
+ const cleanUpPads = async () => {
+ const padIds = ['pad', 'other-pad', 'päd'];
+ await Promise.all(padIds.map(async (padId) => {
+ if (await padManager.doesPadExist(padId)) {
+ const pad = await padManager.getPad(padId);
+ await pad.remove();
+ }
+ }));
};
- assert(socket == null);
- authorize = () => true;
- plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => cb([authorize(req)])}];
- await cleanUpPads();
- });
- afterEach(async function () {
- if (socket) socket.close();
- socket = null;
- await cleanUpPads();
- Object.assign(plugins.hooks, backups.hooks);
- Object.assign(settings, backups.settings);
- });
-
- describe('Normal accesses', function () {
- it('!authn anonymous cookie /p/pad -> 200, ok', async function () {
- const res = await agent.get('/p/pad').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
- it('!authn !cookie -> ok', async function () {
- socket = await common.connect(null);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
- it('!authn user /p/pad -> 200, ok', async function () {
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
- it('authn user /p/pad -> 200, ok', async function () {
- settings.requireAuthentication = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
-
- for (const authn of [false, true]) {
- const desc = authn ? 'authn user' : '!authn anonymous';
- it(`${desc} read-only /p/pad -> 200, ok`, async function () {
- const get = (ep) => {
- let res = agent.get(ep);
- if (authn) res = res.auth('user', 'user-password');
- return res.expect(200);
- };
- settings.requireAuthentication = authn;
- let res = await get('/p/pad');
- socket = await common.connect(res);
- let clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- const readOnlyId = clientVars.data.readOnlyId;
- assert(readOnlyManager.isReadOnlyId(readOnlyId));
- socket.close();
- res = await get(`/p/${readOnlyId}`);
- socket = await common.connect(res);
- clientVars = await common.handshake(socket, readOnlyId);
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, true);
- });
- }
-
- it('authz user /p/pad -> 200, ok', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
- it('supports pad names with characters that must be percent-encoded', async function () {
- settings.requireAuthentication = true;
- // requireAuthorization is set to true here to guarantee that the user's padAuthorizations
- // object is populated. Technically this isn't necessary because the user's padAuthorizations
- // is currently populated even if requireAuthorization is false, but setting this to true
- // ensures the test remains useful if the implementation ever changes.
- settings.requireAuthorization = true;
- const encodedPadId = encodeURIComponent('päd');
- const res = await agent.get(`/p/${encodedPadId}`).auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'päd');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- });
- });
-
- describe('Abnormal access attempts', function () {
- it('authn anonymous /p/pad -> 401, error', async function () {
- settings.requireAuthentication = true;
- const res = await agent.get('/p/pad').expect(401);
- // Despite the 401, try to create the pad via a socket.io connection anyway.
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
-
- it('authn anonymous read-only /p/pad -> 401, error', async function () {
- settings.requireAuthentication = true;
- let res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- const readOnlyId = clientVars.data.readOnlyId;
- assert(readOnlyManager.isReadOnlyId(readOnlyId));
- socket.close();
- res = await agent.get(`/p/${readOnlyId}`).expect(401);
- // Despite the 401, try to read the pad via a socket.io connection anyway.
- socket = await common.connect(res);
- const message = await common.handshake(socket, readOnlyId);
- assert.equal(message.accessStatus, 'deny');
- });
-
- it('authn !cookie -> error', async function () {
- settings.requireAuthentication = true;
- socket = await common.connect(null);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it('authorization bypass attempt -> error', async function () {
- // Only allowed to access /p/pad.
- authorize = (req) => req.path === '/p/pad';
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- // First authenticate and establish a session.
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- // Accessing /p/other-pad should fail, despite the successful fetch of /p/pad.
- const message = await common.handshake(socket, 'other-pad');
- assert.equal(message.accessStatus, 'deny');
- });
- });
-
- describe('Authorization levels via authorize hook', function () {
+ let socket;
+ before(async function () { agent = await common.init(); });
beforeEach(async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- });
-
- it("level='create' -> can create", async function () {
- authorize = () => 'create';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- });
- it('level=true -> can create', async function () {
- authorize = () => true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- });
- it("level='modify' -> can modify", async function () {
- await padManager.getPad('pad'); // Create the pad.
- authorize = () => 'modify';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- });
- it("level='create' settings.editOnly=true -> unable to create", async function () {
- authorize = () => 'create';
- settings.editOnly = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it("level='modify' settings.editOnly=false -> unable to create", async function () {
- authorize = () => 'modify';
- settings.editOnly = false;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it("level='readOnly' -> unable to create", async function () {
- authorize = () => 'readOnly';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it("level='readOnly' -> unable to modify", async function () {
- await padManager.getPad('pad'); // Create the pad.
- authorize = () => 'readOnly';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, true);
- });
- });
-
- describe('Authorization levels via user settings', function () {
- beforeEach(async function () {
- settings.requireAuthentication = true;
- });
-
- it('user.canCreate = true -> can create and modify', async function () {
- settings.users.user.canCreate = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- });
- it('user.canCreate = false -> unable to create', async function () {
- settings.users.user.canCreate = false;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it('user.readOnly = true -> unable to create', async function () {
- settings.users.user.readOnly = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it('user.readOnly = true -> unable to modify', async function () {
- await padManager.getPad('pad'); // Create the pad.
- settings.users.user.readOnly = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, true);
- });
- it('user.readOnly = false -> can create and modify', async function () {
- settings.users.user.readOnly = false;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const clientVars = await common.handshake(socket, 'pad');
- assert.equal(clientVars.type, 'CLIENT_VARS');
- assert.equal(clientVars.data.readonly, false);
- });
- it('user.readOnly = true, user.canCreate = true -> unable to create', async function () {
- settings.users.user.canCreate = true;
- settings.users.user.readOnly = true;
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- });
-
- describe('Authorization level interaction between authorize hook and user settings', function () {
- beforeEach(async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- });
-
- it('authorize hook does not elevate level from user settings', async function () {
- settings.users.user.readOnly = true;
- authorize = () => 'create';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- it('user settings does not elevate level from authorize hook', async function () {
- settings.users.user.readOnly = false;
- settings.users.user.canCreate = true;
- authorize = () => 'readOnly';
- const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
- socket = await common.connect(res);
- const message = await common.handshake(socket, 'pad');
- assert.equal(message.accessStatus, 'deny');
- });
- });
-
- describe('SocketIORouter.js', function () {
- const Module = class {
- setSocketIO(io) {}
- handleConnect(socket) {}
- handleDisconnect(socket) {}
- handleMessage(socket, message) {}
- };
-
- afterEach(async function () {
- socketIoRouter.deleteComponent(this.test.fullTitle());
- socketIoRouter.deleteComponent(`${this.test.fullTitle()} #2`);
- });
-
- it('setSocketIO', async function () {
- let ioServer;
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- setSocketIO(io) { ioServer = io; }
- }());
- assert(ioServer != null);
- });
-
- it('handleConnect', async function () {
- let serverSocket;
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- handleConnect(socket) { serverSocket = socket; }
- }());
- socket = await common.connect();
- assert(serverSocket != null);
- });
-
- it('handleDisconnect', async function () {
- let resolveConnected;
- const connected = new Promise((resolve) => resolveConnected = resolve);
- let resolveDisconnected;
- const disconnected = new Promise((resolve) => resolveDisconnected = resolve);
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- handleConnect(socket) {
- this._socket = socket;
- resolveConnected();
+ backups.hooks = {};
+ for (const hookName of ['preAuthorize', 'authenticate', 'authorize']) {
+ backups.hooks[hookName] = plugins.hooks[hookName];
+ plugins.hooks[hookName] = [];
}
- handleDisconnect(socket) {
- assert(socket != null);
- // There might be lingering disconnect events from sockets created by other tests.
- if (this._socket == null || socket.id !== this._socket.id) return;
- assert.equal(socket, this._socket);
- resolveDisconnected();
+ backups.settings = {};
+ for (const setting of ['editOnly', 'requireAuthentication', 'requireAuthorization', 'users']) {
+ backups.settings[setting] = settings[setting];
}
- }());
- socket = await common.connect();
- await connected;
- socket.close();
- socket = null;
- await disconnected;
- });
-
- it('handleMessage (success)', async function () {
- let serverSocket;
- const want = {
- component: this.test.fullTitle(),
- foo: {bar: 'asdf'},
- };
- let rx;
- const got = new Promise((resolve) => { rx = resolve; });
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- handleConnect(socket) { serverSocket = socket; }
- handleMessage(socket, message) { assert.equal(socket, serverSocket); rx(message); }
- }());
- socketIoRouter.addComponent(`${this.test.fullTitle()} #2`, new class extends Module {
- handleMessage(socket, message) { assert.fail('wrong handler called'); }
- }());
- socket = await common.connect();
- socket.send(want);
- assert.deepEqual(await got, want);
- });
-
- const tx = async (socket, message = {}) => await new Promise((resolve, reject) => {
- const AckErr = class extends Error {
- constructor(name, ...args) { super(...args); this.name = name; }
- };
- socket.send(message,
- (errj, val) => errj != null ? reject(new AckErr(errj.name, errj.message)) : resolve(val));
- });
-
- it('handleMessage with ack (success)', async function () {
- const want = 'value';
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- handleMessage(socket, msg) { return want; }
- }());
- socket = await common.connect();
- const got = await tx(socket, {component: this.test.fullTitle()});
- assert.equal(got, want);
+ settings.editOnly = false;
+ settings.requireAuthentication = false;
+ settings.requireAuthorization = false;
+ settings.users = {
+ admin: { password: 'admin-password', is_admin: true },
+ user: { password: 'user-password' },
+ };
+ assert(socket == null);
+ authorize = () => true;
+ plugins.hooks.authorize = [{ hook_fn: (hookName, { req }, cb) => cb([authorize(req)]) }];
+ await cleanUpPads();
});
-
- it('handleMessage with ack (error)', async function () {
- const InjectedError = class extends Error {
- constructor() { super('injected test error'); this.name = 'InjectedError'; }
- };
- socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
- handleMessage(socket, msg) { throw new InjectedError(); }
- }());
- socket = await common.connect();
- await assert.rejects(tx(socket, {component: this.test.fullTitle()}), new InjectedError());
+ afterEach(async function () {
+ if (socket)
+ socket.close();
+ socket = null;
+ await cleanUpPads();
+ Object.assign(plugins.hooks, backups.hooks);
+ Object.assign(settings, backups.settings);
+ });
+ describe('Normal accesses', function () {
+ it('!authn anonymous cookie /p/pad -> 200, ok', async function () {
+ const res = await agent.get('/p/pad').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ it('!authn !cookie -> ok', async function () {
+ socket = await common.connect(null);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ it('!authn user /p/pad -> 200, ok', async function () {
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ it('authn user /p/pad -> 200, ok', async function () {
+ settings.requireAuthentication = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ for (const authn of [false, true]) {
+ const desc = authn ? 'authn user' : '!authn anonymous';
+ it(`${desc} read-only /p/pad -> 200, ok`, async function () {
+ const get = (ep) => {
+ let res = agent.get(ep);
+ if (authn)
+ res = res.auth('user', 'user-password');
+ return res.expect(200);
+ };
+ settings.requireAuthentication = authn;
+ let res = await get('/p/pad');
+ socket = await common.connect(res);
+ let clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ const readOnlyId = clientVars.data.readOnlyId;
+ assert(readOnlyManager.isReadOnlyId(readOnlyId));
+ socket.close();
+ res = await get(`/p/${readOnlyId}`);
+ socket = await common.connect(res);
+ clientVars = await common.handshake(socket, readOnlyId);
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, true);
+ });
+ }
+ it('authz user /p/pad -> 200, ok', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ it('supports pad names with characters that must be percent-encoded', async function () {
+ settings.requireAuthentication = true;
+ // requireAuthorization is set to true here to guarantee that the user's padAuthorizations
+ // object is populated. Technically this isn't necessary because the user's padAuthorizations
+ // is currently populated even if requireAuthorization is false, but setting this to true
+ // ensures the test remains useful if the implementation ever changes.
+ settings.requireAuthorization = true;
+ const encodedPadId = encodeURIComponent('päd');
+ const res = await agent.get(`/p/${encodedPadId}`).auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'päd');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ });
+ });
+ describe('Abnormal access attempts', function () {
+ it('authn anonymous /p/pad -> 401, error', async function () {
+ settings.requireAuthentication = true;
+ const res = await agent.get('/p/pad').expect(401);
+ // Despite the 401, try to create the pad via a socket.io connection anyway.
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('authn anonymous read-only /p/pad -> 401, error', async function () {
+ settings.requireAuthentication = true;
+ let res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ const readOnlyId = clientVars.data.readOnlyId;
+ assert(readOnlyManager.isReadOnlyId(readOnlyId));
+ socket.close();
+ res = await agent.get(`/p/${readOnlyId}`).expect(401);
+ // Despite the 401, try to read the pad via a socket.io connection anyway.
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, readOnlyId);
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('authn !cookie -> error', async function () {
+ settings.requireAuthentication = true;
+ socket = await common.connect(null);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('authorization bypass attempt -> error', async function () {
+ // Only allowed to access /p/pad.
+ authorize = (req) => req.path === '/p/pad';
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ // First authenticate and establish a session.
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ // Accessing /p/other-pad should fail, despite the successful fetch of /p/pad.
+ const message = await common.handshake(socket, 'other-pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ });
+ describe('Authorization levels via authorize hook', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ });
+ it("level='create' -> can create", async function () {
+ authorize = () => 'create';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ });
+ it('level=true -> can create', async function () {
+ authorize = () => true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ });
+ it("level='modify' -> can modify", async function () {
+ await padManager.getPad('pad'); // Create the pad.
+ authorize = () => 'modify';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ });
+ it("level='create' settings.editOnly=true -> unable to create", async function () {
+ authorize = () => 'create';
+ settings.editOnly = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it("level='modify' settings.editOnly=false -> unable to create", async function () {
+ authorize = () => 'modify';
+ settings.editOnly = false;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it("level='readOnly' -> unable to create", async function () {
+ authorize = () => 'readOnly';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it("level='readOnly' -> unable to modify", async function () {
+ await padManager.getPad('pad'); // Create the pad.
+ authorize = () => 'readOnly';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, true);
+ });
+ });
+ describe('Authorization levels via user settings', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = true;
+ });
+ it('user.canCreate = true -> can create and modify', async function () {
+ settings.users.user.canCreate = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ });
+ it('user.canCreate = false -> unable to create', async function () {
+ settings.users.user.canCreate = false;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('user.readOnly = true -> unable to create', async function () {
+ settings.users.user.readOnly = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('user.readOnly = true -> unable to modify', async function () {
+ await padManager.getPad('pad'); // Create the pad.
+ settings.users.user.readOnly = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, true);
+ });
+ it('user.readOnly = false -> can create and modify', async function () {
+ settings.users.user.readOnly = false;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const clientVars = await common.handshake(socket, 'pad');
+ assert.equal(clientVars.type, 'CLIENT_VARS');
+ assert.equal(clientVars.data.readonly, false);
+ });
+ it('user.readOnly = true, user.canCreate = true -> unable to create', async function () {
+ settings.users.user.canCreate = true;
+ settings.users.user.readOnly = true;
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ });
+ describe('Authorization level interaction between authorize hook and user settings', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ });
+ it('authorize hook does not elevate level from user settings', async function () {
+ settings.users.user.readOnly = true;
+ authorize = () => 'create';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ it('user settings does not elevate level from authorize hook', async function () {
+ settings.users.user.readOnly = false;
+ settings.users.user.canCreate = true;
+ authorize = () => 'readOnly';
+ const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, 'pad');
+ assert.equal(message.accessStatus, 'deny');
+ });
+ });
+ describe('SocketIORouter.js', function () {
+ const Module = class {
+ setSocketIO(io) { }
+ handleConnect(socket) { }
+ handleDisconnect(socket) { }
+ handleMessage(socket, message) { }
+ };
+ afterEach(async function () {
+ socketIoRouter.deleteComponent(this.test.fullTitle());
+ socketIoRouter.deleteComponent(`${this.test.fullTitle()} #2`);
+ });
+ it('setSocketIO', async function () {
+ let ioServer;
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ setSocketIO(io) { ioServer = io; }
+ }());
+ assert(ioServer != null);
+ });
+ it('handleConnect', async function () {
+ let serverSocket;
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ handleConnect(socket) { serverSocket = socket; }
+ }());
+ socket = await common.connect();
+ assert(serverSocket != null);
+ });
+ it('handleDisconnect', async function () {
+ let resolveConnected;
+ const connected = new Promise((resolve) => resolveConnected = resolve);
+ let resolveDisconnected;
+ const disconnected = new Promise((resolve) => resolveDisconnected = resolve);
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ handleConnect(socket) {
+ this._socket = socket;
+ resolveConnected();
+ }
+ handleDisconnect(socket) {
+ assert(socket != null);
+ // There might be lingering disconnect events from sockets created by other tests.
+ if (this._socket == null || socket.id !== this._socket.id)
+ return;
+ assert.equal(socket, this._socket);
+ resolveDisconnected();
+ }
+ }());
+ socket = await common.connect();
+ await connected;
+ socket.close();
+ socket = null;
+ await disconnected;
+ });
+ it('handleMessage (success)', async function () {
+ let serverSocket;
+ const want = {
+ component: this.test.fullTitle(),
+ foo: { bar: 'asdf' },
+ };
+ let rx;
+ const got = new Promise((resolve) => { rx = resolve; });
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ handleConnect(socket) { serverSocket = socket; }
+ handleMessage(socket, message) { assert.equal(socket, serverSocket); rx(message); }
+ }());
+ socketIoRouter.addComponent(`${this.test.fullTitle()} #2`, new class extends Module {
+ handleMessage(socket, message) { assert.fail('wrong handler called'); }
+ }());
+ socket = await common.connect();
+ socket.send(want);
+ assert.deepEqual(await got, want);
+ });
+ const tx = async (socket, message = {}) => await new Promise((resolve, reject) => {
+ const AckErr = class extends Error {
+ constructor(name, ...args) { super(...args); this.name = name; }
+ };
+ socket.send(message, (errj, val) => errj != null ? reject(new AckErr(errj.name, errj.message)) : resolve(val));
+ });
+ it('handleMessage with ack (success)', async function () {
+ const want = 'value';
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ handleMessage(socket, msg) { return want; }
+ }());
+ socket = await common.connect();
+ const got = await tx(socket, { component: this.test.fullTitle() });
+ assert.equal(got, want);
+ });
+ it('handleMessage with ack (error)', async function () {
+ const InjectedError = class extends Error {
+ constructor() { super('injected test error'); this.name = 'InjectedError'; }
+ };
+ socketIoRouter.addComponent(this.test.fullTitle(), new class extends Module {
+ handleMessage(socket, msg) { throw new InjectedError(); }
+ }());
+ socket = await common.connect();
+ await assert.rejects(tx(socket, { component: this.test.fullTitle() }), new InjectedError());
+ });
});
- });
});
diff --git a/src/tests/backend/specs/specialpages.js b/src/tests/backend/specs/specialpages.js
index 93c8b3bc4ac..8be1b00fb8f 100644
--- a/src/tests/backend/specs/specialpages.js
+++ b/src/tests/backend/specs/specialpages.js
@@ -1,28 +1,25 @@
+import * as common from "../common.js";
+import * as settings from "../../../node/utils/Settings.js";
'use strict';
-
-const common = require('../common');
-const settings = require('../../../node/utils/Settings');
-
describe(__filename, function () {
- this.timeout(30000);
- let agent;
- const backups = {};
- before(async function () { agent = await common.init(); });
- beforeEach(async function () {
- backups.settings = {};
- for (const setting of ['requireAuthentication', 'requireAuthorization']) {
- backups.settings[setting] = settings[setting];
- }
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- });
- afterEach(async function () {
- Object.assign(settings, backups.settings);
- });
-
- describe('/javascript', function () {
- it('/javascript -> 200', async function () {
- await agent.get('/javascript').expect(200);
+ this.timeout(30000);
+ let agent;
+ const backups = {};
+ before(async function () { agent = await common.init(); });
+ beforeEach(async function () {
+ backups.settings = {};
+ for (const setting of ['requireAuthentication', 'requireAuthorization']) {
+ backups.settings[setting] = settings[setting];
+ }
+ settings.requireAuthentication = false;
+ settings.requireAuthorization = false;
+ });
+ afterEach(async function () {
+ Object.assign(settings, backups.settings);
+ });
+ describe('/javascript', function () {
+ it('/javascript -> 200', async function () {
+ await agent.get('/javascript').expect(200);
+ });
});
- });
});
diff --git a/src/tests/backend/specs/webaccess.js b/src/tests/backend/specs/webaccess.js
index 23cd2d88968..ced776ca487 100644
--- a/src/tests/backend/specs/webaccess.js
+++ b/src/tests/backend/specs/webaccess.js
@@ -1,494 +1,478 @@
+import assert$0 from "assert";
+import * as common from "../common.js";
+import * as plugins from "../../../static/js/pluginfw/plugin_defs.js";
+import * as settings from "../../../node/utils/Settings.js";
'use strict';
-
-const assert = require('assert').strict;
-const common = require('../common');
-const plugins = require('../../../static/js/pluginfw/plugin_defs');
-const settings = require('../../../node/utils/Settings');
-
+const assert = assert$0.strict;
describe(__filename, function () {
- this.timeout(30000);
- let agent;
- const backups = {};
- const authHookNames = ['preAuthorize', 'authenticate', 'authorize'];
- const failHookNames = ['preAuthzFailure', 'authnFailure', 'authzFailure', 'authFailure'];
- const makeHook = (hookName, hookFn) => ({
- hook_fn: hookFn,
- hook_fn_name: `fake_plugin/${hookName}`,
- hook_name: hookName,
- part: {plugin: 'fake_plugin'},
- });
-
- before(async function () { agent = await common.init(); });
- beforeEach(async function () {
- backups.hooks = {};
- for (const hookName of authHookNames.concat(failHookNames)) {
- backups.hooks[hookName] = plugins.hooks[hookName];
- plugins.hooks[hookName] = [];
- }
- backups.settings = {};
- for (const setting of ['requireAuthentication', 'requireAuthorization', 'users']) {
- backups.settings[setting] = settings[setting];
- }
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- settings.users = {
- admin: {password: 'admin-password', is_admin: true},
- user: {password: 'user-password'},
- };
- });
- afterEach(async function () {
- Object.assign(plugins.hooks, backups.hooks);
- Object.assign(settings, backups.settings);
- });
-
- describe('webaccess: without plugins', function () {
- it('!authn !authz anonymous / -> 200', async function () {
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- await agent.get('/').expect(200);
+ this.timeout(30000);
+ let agent;
+ const backups = {};
+ const authHookNames = ['preAuthorize', 'authenticate', 'authorize'];
+ const failHookNames = ['preAuthzFailure', 'authnFailure', 'authzFailure', 'authFailure'];
+ const makeHook = (hookName, hookFn) => ({
+ hook_fn: hookFn,
+ hook_fn_name: `fake_plugin/${hookName}`,
+ hook_name: hookName,
+ part: { plugin: 'fake_plugin' },
});
- it('!authn !authz anonymous /admin/ -> 401', async function () {
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- await agent.get('/admin/').expect(401);
- });
- it('authn !authz anonymous / -> 401', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- await agent.get('/').expect(401);
- });
- it('authn !authz user / -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- await agent.get('/').auth('user', 'user-password').expect(200);
- });
- it('authn !authz user /admin/ -> 403', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- await agent.get('/admin/').auth('user', 'user-password').expect(403);
- });
- it('authn !authz admin / -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- await agent.get('/').auth('admin', 'admin-password').expect(200);
- });
- it('authn !authz admin /admin/ -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
- });
- it('authn authz anonymous /robots.txt -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/robots.txt').expect(200);
- });
- it('authn authz user / -> 403', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/').auth('user', 'user-password').expect(403);
- });
- it('authn authz user /admin/ -> 403', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/admin/').auth('user', 'user-password').expect(403);
- });
- it('authn authz admin / -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/').auth('admin', 'admin-password').expect(200);
- });
- it('authn authz admin /admin/ -> 200', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
- });
-
- describe('login fails if password is nullish', function () {
- for (const adminPassword of [undefined, null]) {
- // https://tools.ietf.org/html/rfc7617 says that the username and password are sent as
- // base64(username + ':' + password), but there's nothing stopping a malicious user from
- // sending just base64(username) (no colon). The lack of colon could throw off credential
- // parsing, resulting in successful comparisons against a null or undefined password.
- for (const creds of ['admin', 'admin:']) {
- it(`admin password: ${adminPassword} credentials: ${creds}`, async function () {
- settings.users.admin.password = adminPassword;
- const encCreds = Buffer.from(creds).toString('base64');
- await agent.get('/admin/').set('Authorization', `Basic ${encCreds}`).expect(401);
- });
- }
- }
- });
- });
-
- describe('webaccess: preAuthorize, authenticate, and authorize hooks', function () {
- let callOrder;
- const Handler = class {
- constructor(hookName, suffix) {
- this.called = false;
- this.hookName = hookName;
- this.innerHandle = () => [];
- this.id = hookName + suffix;
- this.checkContext = () => {};
- }
- handle(hookName, context, cb) {
- assert.equal(hookName, this.hookName);
- assert(context != null);
- assert(context.req != null);
- assert(context.res != null);
- assert(context.next != null);
- this.checkContext(context);
- assert(!this.called);
- this.called = true;
- callOrder.push(this.id);
- return cb(this.innerHandle(context));
- }
- };
- const handlers = {};
-
+ before(async function () { agent = await common.init(); });
beforeEach(async function () {
- callOrder = [];
- for (const hookName of authHookNames) {
- // Create two handlers for each hook to test deferral to the next function.
- const h0 = new Handler(hookName, '_0');
- const h1 = new Handler(hookName, '_1');
- handlers[hookName] = [h0, h1];
- plugins.hooks[hookName] = [
- makeHook(hookName, h0.handle.bind(h0)),
- makeHook(hookName, h1.handle.bind(h1)),
- ];
- }
- });
-
- describe('preAuthorize', function () {
- beforeEach(async function () {
- settings.requireAuthentication = false;
- settings.requireAuthorization = false;
- });
-
- it('defers if it returns []', async function () {
- await agent.get('/').expect(200);
- // Note: The preAuthorize hook always runs even if requireAuthorization is false.
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
- });
- it('bypasses authenticate and authorize hooks when true is returned', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- handlers.preAuthorize[0].innerHandle = () => [true];
- await agent.get('/').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0']);
- });
- it('bypasses authenticate and authorize hooks when false is returned', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- handlers.preAuthorize[0].innerHandle = () => [false];
- await agent.get('/').expect(403);
- assert.deepEqual(callOrder, ['preAuthorize_0']);
- });
- it('bypasses authenticate and authorize hooks when next is called', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- handlers.preAuthorize[0].innerHandle = ({next}) => next();
- await agent.get('/').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0']);
- });
- it('static content (expressPreSession) bypasses all auth checks', async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- await agent.get('/static/robots.txt').expect(200);
- assert.deepEqual(callOrder, []);
- });
- it('cannot grant access to /admin', async function () {
- handlers.preAuthorize[0].innerHandle = () => [true];
- await agent.get('/admin/').expect(401);
- // Notes:
- // * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
- // 'true' entries are ignored for /admin/* requests.
- // * The authenticate hook always runs for /admin/* requests even if
- // settings.requireAuthentication is false.
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('can deny access to /admin', async function () {
- handlers.preAuthorize[0].innerHandle = () => [false];
- await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
- assert.deepEqual(callOrder, ['preAuthorize_0']);
- });
- it('runs preAuthzFailure hook when access is denied', async function () {
- handlers.preAuthorize[0].innerHandle = () => [false];
- let called = false;
- plugins.hooks.preAuthzFailure = [makeHook('preAuthzFailure', (hookName, {req, res}, cb) => {
- assert.equal(hookName, 'preAuthzFailure');
- assert(req != null);
- assert(res != null);
- assert(!called);
- called = true;
- res.status(200).send('injected');
- return cb([true]);
- })];
- await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
- assert(called);
- });
- it('returns 500 if an exception is thrown', async function () {
- handlers.preAuthorize[0].innerHandle = () => { throw new Error('exception test'); };
- await agent.get('/').expect(500);
- });
- });
-
- describe('authenticate', function () {
- beforeEach(async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = false;
- });
-
- it('is not called if !requireAuthentication and not /admin/*', async function () {
- settings.requireAuthentication = false;
- await agent.get('/').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
- });
- it('is called if !requireAuthentication and /admin/*', async function () {
+ backups.hooks = {};
+ for (const hookName of authHookNames.concat(failHookNames)) {
+ backups.hooks[hookName] = plugins.hooks[hookName];
+ plugins.hooks[hookName] = [];
+ }
+ backups.settings = {};
+ for (const setting of ['requireAuthentication', 'requireAuthorization', 'users']) {
+ backups.settings[setting] = settings[setting];
+ }
settings.requireAuthentication = false;
- await agent.get('/admin/').expect(401);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('defers if empty list returned', async function () {
- await agent.get('/').expect(401);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('does not defer if return [true], 200', async function () {
- handlers.authenticate[0].innerHandle = ({req}) => { req.session.user = {}; return [true]; };
- await agent.get('/').expect(200);
- // Note: authenticate_1 was not called because authenticate_0 handled it.
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
- });
- it('does not defer if return [false], 401', async function () {
- handlers.authenticate[0].innerHandle = () => [false];
- await agent.get('/').expect(401);
- // Note: authenticate_1 was not called because authenticate_0 handled it.
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
- });
- it('falls back to HTTP basic auth', async function () {
- await agent.get('/').auth('user', 'user-password').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('passes settings.users in context', async function () {
- handlers.authenticate[0].checkContext = ({users}) => {
- assert.equal(users, settings.users);
- };
- await agent.get('/').expect(401);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('passes user, password in context if provided', async function () {
- handlers.authenticate[0].checkContext = ({username, password}) => {
- assert.equal(username, 'user');
- assert.equal(password, 'user-password');
- };
- await agent.get('/').auth('user', 'user-password').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('does not pass user, password in context if not provided', async function () {
- handlers.authenticate[0].checkContext = ({username, password}) => {
- assert(username == null);
- assert(password == null);
- };
- await agent.get('/').expect(401);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('errors if req.session.user is not created', async function () {
- handlers.authenticate[0].innerHandle = () => [true];
- await agent.get('/').expect(500);
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
- });
- it('returns 500 if an exception is thrown', async function () {
- handlers.authenticate[0].innerHandle = () => { throw new Error('exception test'); };
- await agent.get('/').expect(500);
- assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
- });
- });
-
- describe('authorize', function () {
- beforeEach(async function () {
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- });
-
- it('is not called if !requireAuthorization (non-/admin)', async function () {
settings.requireAuthorization = false;
- await agent.get('/').auth('user', 'user-password').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('is not called if !requireAuthorization (/admin)', async function () {
- settings.requireAuthorization = false;
- await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1']);
- });
- it('defers if empty list returned', async function () {
- await agent.get('/').auth('user', 'user-password').expect(403);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1',
- 'authorize_0',
- 'authorize_1']);
- });
- it('does not defer if return [true], 200', async function () {
- handlers.authorize[0].innerHandle = () => [true];
- await agent.get('/').auth('user', 'user-password').expect(200);
- // Note: authorize_1 was not called because authorize_0 handled it.
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1',
- 'authorize_0']);
- });
- it('does not defer if return [false], 403', async function () {
- handlers.authorize[0].innerHandle = () => [false];
- await agent.get('/').auth('user', 'user-password').expect(403);
- // Note: authorize_1 was not called because authorize_0 handled it.
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1',
- 'authorize_0']);
- });
- it('passes req.path in context', async function () {
- handlers.authorize[0].checkContext = ({resource}) => {
- assert.equal(resource, '/');
+ settings.users = {
+ admin: { password: 'admin-password', is_admin: true },
+ user: { password: 'user-password' },
};
- await agent.get('/').auth('user', 'user-password').expect(403);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1',
- 'authorize_0',
- 'authorize_1']);
- });
- it('returns 500 if an exception is thrown', async function () {
- handlers.authorize[0].innerHandle = () => { throw new Error('exception test'); };
- await agent.get('/').auth('user', 'user-password').expect(500);
- assert.deepEqual(callOrder, ['preAuthorize_0',
- 'preAuthorize_1',
- 'authenticate_0',
- 'authenticate_1',
- 'authorize_0']);
- });
});
- });
-
- describe('webaccess: authnFailure, authzFailure, authFailure hooks', function () {
- const Handler = class {
- constructor(hookName) {
- this.hookName = hookName;
- this.shouldHandle = false;
- this.called = false;
- }
- handle(hookName, context, cb) {
- assert.equal(hookName, this.hookName);
- assert(context != null);
- assert(context.req != null);
- assert(context.res != null);
- assert(!this.called);
- this.called = true;
- if (this.shouldHandle) {
- context.res.status(200).send(this.hookName);
- return cb([true]);
- }
- return cb([]);
- }
- };
- const handlers = {};
-
- beforeEach(async function () {
- failHookNames.forEach((hookName) => {
- const handler = new Handler(hookName);
- handlers[hookName] = handler;
- plugins.hooks[hookName] = [makeHook(hookName, handler.handle.bind(handler))];
- });
- settings.requireAuthentication = true;
- settings.requireAuthorization = true;
- });
-
- // authn failure tests
- it('authn fail, no hooks handle -> 401', async function () {
- await agent.get('/').expect(401);
- assert(handlers.authnFailure.called);
- assert(!handlers.authzFailure.called);
- assert(handlers.authFailure.called);
- });
- it('authn fail, authnFailure handles', async function () {
- handlers.authnFailure.shouldHandle = true;
- await agent.get('/').expect(200, 'authnFailure');
- assert(handlers.authnFailure.called);
- assert(!handlers.authzFailure.called);
- assert(!handlers.authFailure.called);
- });
- it('authn fail, authFailure handles', async function () {
- handlers.authFailure.shouldHandle = true;
- await agent.get('/').expect(200, 'authFailure');
- assert(handlers.authnFailure.called);
- assert(!handlers.authzFailure.called);
- assert(handlers.authFailure.called);
+ afterEach(async function () {
+ Object.assign(plugins.hooks, backups.hooks);
+ Object.assign(settings, backups.settings);
});
- it('authnFailure trumps authFailure', async function () {
- handlers.authnFailure.shouldHandle = true;
- handlers.authFailure.shouldHandle = true;
- await agent.get('/').expect(200, 'authnFailure');
- assert(handlers.authnFailure.called);
- assert(!handlers.authFailure.called);
+ describe('webaccess: without plugins', function () {
+ it('!authn !authz anonymous / -> 200', async function () {
+ settings.requireAuthentication = false;
+ settings.requireAuthorization = false;
+ await agent.get('/').expect(200);
+ });
+ it('!authn !authz anonymous /admin/ -> 401', async function () {
+ settings.requireAuthentication = false;
+ settings.requireAuthorization = false;
+ await agent.get('/admin/').expect(401);
+ });
+ it('authn !authz anonymous / -> 401', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ await agent.get('/').expect(401);
+ });
+ it('authn !authz user / -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ await agent.get('/').auth('user', 'user-password').expect(200);
+ });
+ it('authn !authz user /admin/ -> 403', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ await agent.get('/admin/').auth('user', 'user-password').expect(403);
+ });
+ it('authn !authz admin / -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ await agent.get('/').auth('admin', 'admin-password').expect(200);
+ });
+ it('authn !authz admin /admin/ -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
+ });
+ it('authn authz anonymous /robots.txt -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/robots.txt').expect(200);
+ });
+ it('authn authz user / -> 403', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/').auth('user', 'user-password').expect(403);
+ });
+ it('authn authz user /admin/ -> 403', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/admin/').auth('user', 'user-password').expect(403);
+ });
+ it('authn authz admin / -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/').auth('admin', 'admin-password').expect(200);
+ });
+ it('authn authz admin /admin/ -> 200', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
+ });
+ describe('login fails if password is nullish', function () {
+ for (const adminPassword of [undefined, null]) {
+ // https://tools.ietf.org/html/rfc7617 says that the username and password are sent as
+ // base64(username + ':' + password), but there's nothing stopping a malicious user from
+ // sending just base64(username) (no colon). The lack of colon could throw off credential
+ // parsing, resulting in successful comparisons against a null or undefined password.
+ for (const creds of ['admin', 'admin:']) {
+ it(`admin password: ${adminPassword} credentials: ${creds}`, async function () {
+ settings.users.admin.password = adminPassword;
+ const encCreds = Buffer.from(creds).toString('base64');
+ await agent.get('/admin/').set('Authorization', `Basic ${encCreds}`).expect(401);
+ });
+ }
+ }
+ });
});
-
- // authz failure tests
- it('authz fail, no hooks handle -> 403', async function () {
- await agent.get('/').auth('user', 'user-password').expect(403);
- assert(!handlers.authnFailure.called);
- assert(handlers.authzFailure.called);
- assert(handlers.authFailure.called);
- });
- it('authz fail, authzFailure handles', async function () {
- handlers.authzFailure.shouldHandle = true;
- await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
- assert(!handlers.authnFailure.called);
- assert(handlers.authzFailure.called);
- assert(!handlers.authFailure.called);
- });
- it('authz fail, authFailure handles', async function () {
- handlers.authFailure.shouldHandle = true;
- await agent.get('/').auth('user', 'user-password').expect(200, 'authFailure');
- assert(!handlers.authnFailure.called);
- assert(handlers.authzFailure.called);
- assert(handlers.authFailure.called);
+ describe('webaccess: preAuthorize, authenticate, and authorize hooks', function () {
+ let callOrder;
+ const Handler = class {
+ constructor(hookName, suffix) {
+ this.called = false;
+ this.hookName = hookName;
+ this.innerHandle = () => [];
+ this.id = hookName + suffix;
+ this.checkContext = () => { };
+ }
+ handle(hookName, context, cb) {
+ assert.equal(hookName, this.hookName);
+ assert(context != null);
+ assert(context.req != null);
+ assert(context.res != null);
+ assert(context.next != null);
+ this.checkContext(context);
+ assert(!this.called);
+ this.called = true;
+ callOrder.push(this.id);
+ return cb(this.innerHandle(context));
+ }
+ };
+ const handlers = {};
+ beforeEach(async function () {
+ callOrder = [];
+ for (const hookName of authHookNames) {
+ // Create two handlers for each hook to test deferral to the next function.
+ const h0 = new Handler(hookName, '_0');
+ const h1 = new Handler(hookName, '_1');
+ handlers[hookName] = [h0, h1];
+ plugins.hooks[hookName] = [
+ makeHook(hookName, h0.handle.bind(h0)),
+ makeHook(hookName, h1.handle.bind(h1)),
+ ];
+ }
+ });
+ describe('preAuthorize', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = false;
+ settings.requireAuthorization = false;
+ });
+ it('defers if it returns []', async function () {
+ await agent.get('/').expect(200);
+ // Note: The preAuthorize hook always runs even if requireAuthorization is false.
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
+ });
+ it('bypasses authenticate and authorize hooks when true is returned', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ handlers.preAuthorize[0].innerHandle = () => [true];
+ await agent.get('/').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0']);
+ });
+ it('bypasses authenticate and authorize hooks when false is returned', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ handlers.preAuthorize[0].innerHandle = () => [false];
+ await agent.get('/').expect(403);
+ assert.deepEqual(callOrder, ['preAuthorize_0']);
+ });
+ it('bypasses authenticate and authorize hooks when next is called', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ handlers.preAuthorize[0].innerHandle = ({ next }) => next();
+ await agent.get('/').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0']);
+ });
+ it('static content (expressPreSession) bypasses all auth checks', async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ await agent.get('/static/robots.txt').expect(200);
+ assert.deepEqual(callOrder, []);
+ });
+ it('cannot grant access to /admin', async function () {
+ handlers.preAuthorize[0].innerHandle = () => [true];
+ await agent.get('/admin/').expect(401);
+ // Notes:
+ // * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
+ // 'true' entries are ignored for /admin/* requests.
+ // * The authenticate hook always runs for /admin/* requests even if
+ // settings.requireAuthentication is false.
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('can deny access to /admin', async function () {
+ handlers.preAuthorize[0].innerHandle = () => [false];
+ await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
+ assert.deepEqual(callOrder, ['preAuthorize_0']);
+ });
+ it('runs preAuthzFailure hook when access is denied', async function () {
+ handlers.preAuthorize[0].innerHandle = () => [false];
+ let called = false;
+ plugins.hooks.preAuthzFailure = [makeHook('preAuthzFailure', (hookName, { req, res }, cb) => {
+ assert.equal(hookName, 'preAuthzFailure');
+ assert(req != null);
+ assert(res != null);
+ assert(!called);
+ called = true;
+ res.status(200).send('injected');
+ return cb([true]);
+ })];
+ await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
+ assert(called);
+ });
+ it('returns 500 if an exception is thrown', async function () {
+ handlers.preAuthorize[0].innerHandle = () => { throw new Error('exception test'); };
+ await agent.get('/').expect(500);
+ });
+ });
+ describe('authenticate', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ });
+ it('is not called if !requireAuthentication and not /admin/*', async function () {
+ settings.requireAuthentication = false;
+ await agent.get('/').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
+ });
+ it('is called if !requireAuthentication and /admin/*', async function () {
+ settings.requireAuthentication = false;
+ await agent.get('/admin/').expect(401);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('defers if empty list returned', async function () {
+ await agent.get('/').expect(401);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('does not defer if return [true], 200', async function () {
+ handlers.authenticate[0].innerHandle = ({ req }) => { req.session.user = {}; return [true]; };
+ await agent.get('/').expect(200);
+ // Note: authenticate_1 was not called because authenticate_0 handled it.
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
+ });
+ it('does not defer if return [false], 401', async function () {
+ handlers.authenticate[0].innerHandle = () => [false];
+ await agent.get('/').expect(401);
+ // Note: authenticate_1 was not called because authenticate_0 handled it.
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
+ });
+ it('falls back to HTTP basic auth', async function () {
+ await agent.get('/').auth('user', 'user-password').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('passes settings.users in context', async function () {
+ handlers.authenticate[0].checkContext = ({ users }) => {
+ assert.equal(users, settings.users);
+ };
+ await agent.get('/').expect(401);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('passes user, password in context if provided', async function () {
+ handlers.authenticate[0].checkContext = ({ username, password }) => {
+ assert.equal(username, 'user');
+ assert.equal(password, 'user-password');
+ };
+ await agent.get('/').auth('user', 'user-password').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('does not pass user, password in context if not provided', async function () {
+ handlers.authenticate[0].checkContext = ({ username, password }) => {
+ assert(username == null);
+ assert(password == null);
+ };
+ await agent.get('/').expect(401);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('errors if req.session.user is not created', async function () {
+ handlers.authenticate[0].innerHandle = () => [true];
+ await agent.get('/').expect(500);
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
+ });
+ it('returns 500 if an exception is thrown', async function () {
+ handlers.authenticate[0].innerHandle = () => { throw new Error('exception test'); };
+ await agent.get('/').expect(500);
+ assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0']);
+ });
+ });
+ describe('authorize', function () {
+ beforeEach(async function () {
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ });
+ it('is not called if !requireAuthorization (non-/admin)', async function () {
+ settings.requireAuthorization = false;
+ await agent.get('/').auth('user', 'user-password').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('is not called if !requireAuthorization (/admin)', async function () {
+ settings.requireAuthorization = false;
+ await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1']);
+ });
+ it('defers if empty list returned', async function () {
+ await agent.get('/').auth('user', 'user-password').expect(403);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1',
+ 'authorize_0',
+ 'authorize_1']);
+ });
+ it('does not defer if return [true], 200', async function () {
+ handlers.authorize[0].innerHandle = () => [true];
+ await agent.get('/').auth('user', 'user-password').expect(200);
+ // Note: authorize_1 was not called because authorize_0 handled it.
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1',
+ 'authorize_0']);
+ });
+ it('does not defer if return [false], 403', async function () {
+ handlers.authorize[0].innerHandle = () => [false];
+ await agent.get('/').auth('user', 'user-password').expect(403);
+ // Note: authorize_1 was not called because authorize_0 handled it.
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1',
+ 'authorize_0']);
+ });
+ it('passes req.path in context', async function () {
+ handlers.authorize[0].checkContext = ({ resource }) => {
+ assert.equal(resource, '/');
+ };
+ await agent.get('/').auth('user', 'user-password').expect(403);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1',
+ 'authorize_0',
+ 'authorize_1']);
+ });
+ it('returns 500 if an exception is thrown', async function () {
+ handlers.authorize[0].innerHandle = () => { throw new Error('exception test'); };
+ await agent.get('/').auth('user', 'user-password').expect(500);
+ assert.deepEqual(callOrder, ['preAuthorize_0',
+ 'preAuthorize_1',
+ 'authenticate_0',
+ 'authenticate_1',
+ 'authorize_0']);
+ });
+ });
});
- it('authzFailure trumps authFailure', async function () {
- handlers.authzFailure.shouldHandle = true;
- handlers.authFailure.shouldHandle = true;
- await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
- assert(handlers.authzFailure.called);
- assert(!handlers.authFailure.called);
+ describe('webaccess: authnFailure, authzFailure, authFailure hooks', function () {
+ const Handler = class {
+ constructor(hookName) {
+ this.hookName = hookName;
+ this.shouldHandle = false;
+ this.called = false;
+ }
+ handle(hookName, context, cb) {
+ assert.equal(hookName, this.hookName);
+ assert(context != null);
+ assert(context.req != null);
+ assert(context.res != null);
+ assert(!this.called);
+ this.called = true;
+ if (this.shouldHandle) {
+ context.res.status(200).send(this.hookName);
+ return cb([true]);
+ }
+ return cb([]);
+ }
+ };
+ const handlers = {};
+ beforeEach(async function () {
+ failHookNames.forEach((hookName) => {
+ const handler = new Handler(hookName);
+ handlers[hookName] = handler;
+ plugins.hooks[hookName] = [makeHook(hookName, handler.handle.bind(handler))];
+ });
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = true;
+ });
+ // authn failure tests
+ it('authn fail, no hooks handle -> 401', async function () {
+ await agent.get('/').expect(401);
+ assert(handlers.authnFailure.called);
+ assert(!handlers.authzFailure.called);
+ assert(handlers.authFailure.called);
+ });
+ it('authn fail, authnFailure handles', async function () {
+ handlers.authnFailure.shouldHandle = true;
+ await agent.get('/').expect(200, 'authnFailure');
+ assert(handlers.authnFailure.called);
+ assert(!handlers.authzFailure.called);
+ assert(!handlers.authFailure.called);
+ });
+ it('authn fail, authFailure handles', async function () {
+ handlers.authFailure.shouldHandle = true;
+ await agent.get('/').expect(200, 'authFailure');
+ assert(handlers.authnFailure.called);
+ assert(!handlers.authzFailure.called);
+ assert(handlers.authFailure.called);
+ });
+ it('authnFailure trumps authFailure', async function () {
+ handlers.authnFailure.shouldHandle = true;
+ handlers.authFailure.shouldHandle = true;
+ await agent.get('/').expect(200, 'authnFailure');
+ assert(handlers.authnFailure.called);
+ assert(!handlers.authFailure.called);
+ });
+ // authz failure tests
+ it('authz fail, no hooks handle -> 403', async function () {
+ await agent.get('/').auth('user', 'user-password').expect(403);
+ assert(!handlers.authnFailure.called);
+ assert(handlers.authzFailure.called);
+ assert(handlers.authFailure.called);
+ });
+ it('authz fail, authzFailure handles', async function () {
+ handlers.authzFailure.shouldHandle = true;
+ await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
+ assert(!handlers.authnFailure.called);
+ assert(handlers.authzFailure.called);
+ assert(!handlers.authFailure.called);
+ });
+ it('authz fail, authFailure handles', async function () {
+ handlers.authFailure.shouldHandle = true;
+ await agent.get('/').auth('user', 'user-password').expect(200, 'authFailure');
+ assert(!handlers.authnFailure.called);
+ assert(handlers.authzFailure.called);
+ assert(handlers.authFailure.called);
+ });
+ it('authzFailure trumps authFailure', async function () {
+ handlers.authzFailure.shouldHandle = true;
+ handlers.authFailure.shouldHandle = true;
+ await agent.get('/').auth('user', 'user-password').expect(200, 'authzFailure');
+ assert(handlers.authzFailure.called);
+ assert(!handlers.authFailure.called);
+ });
});
- });
});
diff --git a/src/tests/frontend/specs/admintroubleshooting.js b/src/tests/frontend/specs/admintroubleshooting.js
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/specs/adminupdateplugins.js b/src/tests/frontend/specs/adminupdateplugins.js
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/specs/multiple_authors_clear_authorship_colors.js b/src/tests/frontend/specs/multiple_authors_clear_authorship_colors.js
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/specs/scrollTo.js b/src/tests/frontend/specs/scrollTo.js
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/travis/adminrunner.sh b/src/tests/frontend/travis/adminrunner.sh
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/travis/runner.sh b/src/tests/frontend/travis/runner.sh
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/travis/runnerBackend.sh b/src/tests/frontend/travis/runnerBackend.sh
old mode 100755
new mode 100644
diff --git a/src/tests/frontend/travis/runnerLoadTest.sh b/src/tests/frontend/travis/runnerLoadTest.sh
old mode 100755
new mode 100644
diff --git a/src/tests/ratelimit/testlimits.sh b/src/tests/ratelimit/testlimits.sh
old mode 100755
new mode 100644
diff --git a/src/tsconfig.json b/src/tsconfig.json
new file mode 100644
index 00000000000..a27cc7201d2
--- /dev/null
+++ b/src/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "typeRoots": ["node_modules/@types"],
+ "types": ["node", "jquery"],
+ "module": "esnext",
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "sourceMap": true,
+ "target": "ES2015",
+ "outDir": "dist"
+ },
+ "exclude": ["node_modules"],
+ "lib": ["es2015"]
+}
diff --git a/start.bat b/start.bat
index 7e9264ee3b6..1bdbf994302 100644
--- a/start.bat
+++ b/start.bat
@@ -1,11 +1,11 @@
-@echo off
-REM Windows and symlinks do not get along with each other, so on Windows
-REM `node_modules\ep_etherpad-lite` is sometimes a full copy of `src` not a
-REM symlink to `src`. If it is a copy, Node.js sees `src\foo.js` and
-REM `node_modules\ep_etherpad-lite\foo.js` as two independent modules with
-REM independent state, when they should be treated as the same file. To work
-REM around this, everything must consistently use either `src` or
-REM `node_modules\ep_etherpad-lite` on Windows. Because some plugins access
-REM Etherpad internals via `require('ep_etherpad-lite/foo')`,
-REM `node_modules\ep_etherpad-lite` is used here.
-node node_modules\ep_etherpad-lite\node\server.js
+@echo off
+REM Windows and symlinks do not get along with each other, so on Windows
+REM `node_modules\ep_etherpad-lite` is sometimes a full copy of `src` not a
+REM symlink to `src`. If it is a copy, Node.js sees `src\foo.js` and
+REM `node_modules\ep_etherpad-lite\foo.js` as two independent modules with
+REM independent state, when they should be treated as the same file. To work
+REM around this, everything must consistently use either `src` or
+REM `node_modules\ep_etherpad-lite` on Windows. Because some plugins access
+REM Etherpad internals via `require('ep_etherpad-lite/foo')`,
+REM `node_modules\ep_etherpad-lite` is used here.
+node node_modules\ep_etherpad-lite\node\server.js