Import upstream patches from The Lounge (Feb 2024), bump version to v4.4.1-2 #2
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -12,9 +12,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# EOL: April 2024
|
||||
- os: ubuntu-latest
|
||||
node_version: 16.x
|
||||
# EOL: April 2025
|
||||
- os: macOS-latest
|
||||
node_version: 18.x
|
||||
- os: windows-latest
|
||||
@ -25,6 +23,9 @@ jobs:
|
||||
# EOL: April 2026
|
||||
- os: ubuntu-latest
|
||||
node_version: 20.x
|
||||
# EOL: April/June 2024
|
||||
- os: ubuntu-latest
|
||||
node_version: 21.x
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
@ -110,26 +110,23 @@ router.beforeEach((to, from, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach((to, from) => {
|
||||
// Disallow navigating to non-existing routes
|
||||
if (!to.matched.length) {
|
||||
next(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disallow navigating to invalid channels
|
||||
if (to.name === "RoutedChat" && !store.getters.findChannel(Number(to.params.id))) {
|
||||
next(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disallow navigating to invalid networks
|
||||
if (to.name === "NetworkEdit" && !store.getters.findNetwork(String(to.params.uuid))) {
|
||||
next(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
next();
|
||||
return true;
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
|
@ -11,6 +11,7 @@
|
||||
"../server/log.ts",
|
||||
"../server/config.ts",
|
||||
"../server/client.ts",
|
||||
"../server/storageCleaner.ts",
|
||||
"../server/clientManager.ts",
|
||||
"../server/identification.ts",
|
||||
"../server/plugins/changelog.ts",
|
||||
@ -44,14 +45,16 @@
|
||||
"compilerOptions": {
|
||||
"sourceMap": false /*Create source map files for emitted JavaScript files. See more: https://www.typescriptlang.org/tsconfig#sourceMap */,
|
||||
"jsx": "preserve" /* Specify what JSX code is generated. */,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
// this enables stricter inference for data properties on `this`
|
||||
"strict": true,
|
||||
// if using webpack 2+ or rollup, to leverage tree shaking:
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
|
||||
// TODO: Remove eventually
|
||||
"noImplicitAny": false /*Enable error reporting for expressions and declarations with an implied any type. See more: https://www.typescriptlang.org/tsconfig#noImplicitAny */
|
||||
} /* Instructs the TypeScript compiler how to compile .ts files. */
|
||||
|
@ -308,6 +308,26 @@ module.exports = {
|
||||
// This value is set to `["sqlite", "text"]` by default.
|
||||
messageStorage: ["sqlite", "text"],
|
||||
|
||||
// ### `storagePolicy`
|
||||
|
||||
// When the sqlite storage is in use, control the maximum storage duration.
|
||||
// A background task will periodically clean up messages older than the limit.
|
||||
|
||||
// The available keys for the `storagePolicy` object are:
|
||||
//
|
||||
// - `enabled`: If this is false, the cleaning task is not running.
|
||||
// - `maxAgeDays`: Maximum age of an entry in days.
|
||||
// - `deletionPolicy`: Controls what types of messages are being deleted.
|
||||
// Valid options are:
|
||||
// - `statusOnly`: Only delete message types which are status related (e.g. away, back, join, parts, mode, ctcp...)
|
||||
// but keep actual messages from nicks. This keeps the DB size down while retaining "precious" messages.
|
||||
// - `everything`: Delete everything, including messages from irc nicks
|
||||
storagePolicy: {
|
||||
enabled: false,
|
||||
maxAgeDays: 7,
|
||||
deletionPolicy: "statusOnly",
|
||||
},
|
||||
|
||||
// ### `useHexIp`
|
||||
//
|
||||
// When set to `true`, users' IP addresses will be encoded as hex.
|
||||
|
24
package.json
24
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@supernets/hardlounge",
|
||||
"description": "The self-hosted Web IRC client",
|
||||
"version": "4.4.1-1",
|
||||
"version": "4.4.1-2",
|
||||
"preferGlobal": true,
|
||||
"bin": {
|
||||
"hardlounge": "index.js"
|
||||
@ -24,8 +24,9 @@
|
||||
"lint:stylelint": "stylelint --color \"client/**/*.css\"",
|
||||
"lint": "run-p --aggregate-output --continue-on-error lint:*",
|
||||
"start": "node index start",
|
||||
"test": "run-p --aggregate-output --continue-on-error lint:* test:*",
|
||||
"test:mocha": "cross-env NODE_ENV=test webpack --mode=development && cross-env NODE_ENV=test TS_NODE_PROJECT='./test/tsconfig.json' nyc --nycrc-path=test/.nycrc-mocha.json mocha --require ts-node/register --colors --config=test/.mocharc.yml",
|
||||
"test": "run-p --aggregate-output --continue-on-error lint:* test:mocha",
|
||||
"test:mocha": "webpack --mode=development && cross-env NODE_ENV=test TS_NODE_PROJECT='./test/tsconfig.json' mocha --config=test/.mocharc.yml 'test/**/*.ts'",
|
||||
"test:nospec": "webpack --mode=development && cross-env NODE_ENV=test TS_NODE_PROJECT='./test/tsconfig.json' mocha --config=test/.mocharc.yml",
|
||||
"watch": "webpack --watch"
|
||||
},
|
||||
"keywords": [
|
||||
@ -40,7 +41,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"./.thelounge_home",
|
||||
@ -52,6 +53,7 @@
|
||||
"./public/**"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/traverse": "7.23.6",
|
||||
"@fastify/busboy": "1.0.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"caniuse-lite": "^1.0.30001561",
|
||||
@ -83,7 +85,7 @@
|
||||
"yarn": "1.22.17"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sqlite3": "5.1.6"
|
||||
"sqlite3": "5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.17.10",
|
||||
@ -92,15 +94,15 @@
|
||||
"@fortawesome/fontawesome-free": "5.15.4",
|
||||
"@istanbuljs/nyc-config-typescript": "1.0.2",
|
||||
"@textcomplete/core": "0.1.10",
|
||||
"@textcomplete/textarea": "0.1.12",
|
||||
"@types/bcryptjs": "2.4.5",
|
||||
"@textcomplete/textarea": "0.1.13",
|
||||
"@types/bcryptjs": "2.4.6",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/cheerio": "0.22.33",
|
||||
"@types/content-disposition": "0.5.7",
|
||||
"@types/cheerio": "0.22.35",
|
||||
"@types/content-disposition": "0.5.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/is-utf8": "0.2.2",
|
||||
"@types/is-utf8": "0.2.3",
|
||||
"@types/ldapjs": "2.2.2",
|
||||
"@types/linkify-it": "3.0.3",
|
||||
"@types/linkify-it": "3.0.5",
|
||||
"@types/lodash": "4.14.200",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/mocha": "9.1.1",
|
||||
|
@ -18,6 +18,7 @@ import TextFileMessageStorage from "./plugins/messageStorage/text";
|
||||
import Network, {IgnoreListItem, NetworkConfig, NetworkWithIrcFramework} from "./models/network";
|
||||
import ClientManager from "./clientManager";
|
||||
import {MessageStorage, SearchQuery, SearchResponse} from "./plugins/messageStorage/types";
|
||||
import { StorageCleaner } from "./storageCleaner";
|
||||
|
||||
type OrderItem = Chan["id"] | Network["uuid"];
|
||||
type Order = OrderItem[];
|
||||
@ -138,6 +139,13 @@ class Client {
|
||||
if (!Config.values.public && client.config.log) {
|
||||
if (Config.values.messageStorage.includes("sqlite")) {
|
||||
client.messageProvider = new SqliteMessageStorage(client.name);
|
||||
if (Config.values.storagePolicy.enabled) {
|
||||
log.info(
|
||||
`Activating storage cleaner. Policy: ${Config.values.storagePolicy.deletionPolicy}. MaxAge: ${Config.values.storagePolicy.maxAgeDays} days`
|
||||
);
|
||||
const cleaner = new StorageCleaner(client.messageProvider);
|
||||
cleaner.start();
|
||||
}
|
||||
client.messageStorage.push(client.messageProvider);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { Command } from "commander";
|
||||
import ClientManager from "../clientManager";
|
||||
import Utils from "./utils";
|
||||
import SqliteMessageStorage from "../plugins/messageStorage/sqlite";
|
||||
import {StorageCleaner} from "../storageCleaner";
|
||||
|
||||
const program = new Command("storage").description(
|
||||
"various utilities related to the message storage"
|
||||
@ -10,7 +11,7 @@ const program = new Command("storage").description(
|
||||
|
||||
program
|
||||
.command("migrate")
|
||||
.argument("[user]", "migrate a specific user only, all if not provided")
|
||||
.argument("[username]", "migrate a specific user only, all if not provided")
|
||||
.description("Migrate message storage where needed")
|
||||
.on("--help", Utils.extraHelp)
|
||||
.action(function (user) {
|
||||
@ -20,7 +21,19 @@ program
|
||||
});
|
||||
});
|
||||
|
||||
async function runMigrations(user: string) {
|
||||
program
|
||||
.command("clean")
|
||||
.argument("[user]", "clean messages for a specific user only, all if not provided")
|
||||
.description("Delete messages from the DB based on the storage policy")
|
||||
.on("--help", Utils.extraHelp)
|
||||
.action(function (user) {
|
||||
runCleaning(user).catch((err) => {
|
||||
log.error(err.toString());
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
async function runMigrations(user?: string) {
|
||||
const manager = new ClientManager();
|
||||
const users = manager.getUsers();
|
||||
|
||||
@ -65,4 +78,46 @@ function isUserLogEnabled(manager: ClientManager, user: string): boolean {
|
||||
return conf.log;
|
||||
}
|
||||
|
||||
async function runCleaning(user: string) {
|
||||
const manager = new ClientManager();
|
||||
const users = manager.getUsers();
|
||||
|
||||
if (user) {
|
||||
if (!users.includes(user)) {
|
||||
throw new Error(`invalid user ${user}`);
|
||||
}
|
||||
|
||||
return cleanUser(manager, user);
|
||||
}
|
||||
|
||||
for (const name of users) {
|
||||
await cleanUser(manager, name);
|
||||
// if any migration fails we blow up,
|
||||
// chances are the rest won't complete either
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanUser(manager: ClientManager, user: string) {
|
||||
log.info("handling user", user);
|
||||
|
||||
if (!isUserLogEnabled(manager, user)) {
|
||||
log.info("logging disabled for user", user, ". Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
const sqlite = new SqliteMessageStorage(user);
|
||||
await sqlite.enable();
|
||||
const cleaner = new StorageCleaner(sqlite);
|
||||
const num_deleted = await cleaner.runDeletesNoLimit();
|
||||
log.info(`deleted ${num_deleted} messages`);
|
||||
log.info("running a vacuum now, this might take a while");
|
||||
|
||||
if (num_deleted > 0) {
|
||||
await sqlite.vacuum();
|
||||
}
|
||||
|
||||
await sqlite.close();
|
||||
log.info(`cleaning messages for ${user} has been successful`);
|
||||
}
|
||||
|
||||
export default program;
|
||||
|
@ -143,11 +143,11 @@ class Utils {
|
||||
data.toString()
|
||||
.trim()
|
||||
.split("\n")
|
||||
.forEach((line) => {
|
||||
.forEach((line: string) => {
|
||||
try {
|
||||
line = JSON.parse(line);
|
||||
const json = JSON.parse(line);
|
||||
|
||||
if (line.type === "success") {
|
||||
if (json.type === "success") {
|
||||
success = true;
|
||||
}
|
||||
} catch (e: any) {
|
||||
@ -163,11 +163,24 @@ class Utils {
|
||||
.trim()
|
||||
.split("\n")
|
||||
.forEach((line: string) => {
|
||||
const json = JSON.parse(line);
|
||||
try {
|
||||
const json = JSON.parse(line);
|
||||
|
||||
if (json.type === "error") {
|
||||
log.error(json.data);
|
||||
switch (json.type) {
|
||||
case "error":
|
||||
log.error(json.data);
|
||||
break;
|
||||
case "warning":
|
||||
// this includes pointless things like "ignored scripts due to flag"
|
||||
// so let's hide it
|
||||
break;
|
||||
}
|
||||
return;
|
||||
} catch (e: any) {
|
||||
// we simply fall through and log at debug... chances are there's nothing the user can do about it
|
||||
// as it includes things like deprecation warnings, but we might want to know as developers
|
||||
}
|
||||
log.debug(line);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -76,6 +76,12 @@ type Debug = {
|
||||
raw: boolean;
|
||||
};
|
||||
|
||||
type StoragePolicy = {
|
||||
enabled: boolean;
|
||||
maxAgeDays: number;
|
||||
deletionPolicy: "statusOnly" | "everything";
|
||||
};
|
||||
|
||||
export type ConfigType = {
|
||||
public: boolean;
|
||||
host: string | undefined;
|
||||
@ -97,6 +103,7 @@ export type ConfigType = {
|
||||
defaults: Defaults;
|
||||
lockNetwork: boolean;
|
||||
messageStorage: string[];
|
||||
storagePolicy: StoragePolicy;
|
||||
useHexIp: boolean;
|
||||
webirc?: WebIRC;
|
||||
identd: Identd;
|
||||
|
@ -11,8 +11,10 @@ import type {
|
||||
SearchResponse,
|
||||
SearchQuery,
|
||||
SearchableMessageStorage,
|
||||
DeletionRequest
|
||||
} from "./types";
|
||||
import Network from "../../models/network";
|
||||
import { threadId } from "worker_threads";
|
||||
|
||||
// TODO; type
|
||||
let sqlite3: any;
|
||||
@ -36,7 +38,7 @@ type Rollback = {
|
||||
stmts: string[];
|
||||
};
|
||||
|
||||
export const currentSchemaVersion = 1679743888000; // use `new Date().getTime()`
|
||||
export const currentSchemaVersion = 1703322560448; // use `new Date().getTime()`
|
||||
|
||||
// Desired schema, adapt to the newest version and add migrations to the array below
|
||||
const schema = [
|
||||
@ -55,6 +57,7 @@ const schema = [
|
||||
)`,
|
||||
"CREATE INDEX network_channel ON messages (network, channel)",
|
||||
"CREATE INDEX time ON messages (time)",
|
||||
"CREATE INDEX msg_type_idx on messages (type)",
|
||||
];
|
||||
|
||||
// the migrations will be executed in an exclusive transaction as a whole
|
||||
@ -88,6 +91,10 @@ export const migrations: Migration[] = [
|
||||
)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
version: 1703322560448,
|
||||
stmts: ["CREATE INDEX msg_type_idx on messages (type)"]
|
||||
}
|
||||
];
|
||||
|
||||
// down migrations need to restore the state of the prior version.
|
||||
@ -101,6 +108,10 @@ export const rollbacks: Rollback[] = [
|
||||
version: 1679743888000,
|
||||
stmts: [], // here we can't drop the tables, as we use them in the code, so just leave those in
|
||||
},
|
||||
{
|
||||
version: 1703322560448,
|
||||
stmts: ["drop INDEX msg_type_idx"]
|
||||
}
|
||||
];
|
||||
|
||||
class Deferred {
|
||||
@ -126,19 +137,8 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
this.initDone = new Deferred();
|
||||
}
|
||||
|
||||
async _enable() {
|
||||
const logsPath = Config.getUserLogsPath();
|
||||
const sqlitePath = path.join(logsPath, `${this.userName}.sqlite3`);
|
||||
|
||||
try {
|
||||
await fs.mkdir(logsPath, { recursive: true });
|
||||
} catch (e) {
|
||||
throw Helper.catch_to_error("Unable to create logs directory", e);
|
||||
}
|
||||
|
||||
this.isEnabled = true;
|
||||
|
||||
this.database = new sqlite3.Database(sqlitePath);
|
||||
async _enable(connection_string: string) {
|
||||
this.database = new sqlite3.Database(connection_string);
|
||||
|
||||
try {
|
||||
await this.run_pragmas(); // must be done outside of a transaction
|
||||
@ -147,11 +147,22 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
this.isEnabled = false;
|
||||
throw Helper.catch_to_error("Migration failed", e);
|
||||
}
|
||||
|
||||
this.isEnabled = true;
|
||||
}
|
||||
|
||||
async enable() {
|
||||
const logsPath = Config.getUserLogsPath();
|
||||
const sqlitePath = path.join(logsPath, `${this.userName}.sqlite3`);
|
||||
|
||||
try {
|
||||
await this._enable();
|
||||
await fs.mkdir(logsPath, {recursive: true});
|
||||
} catch (e) {
|
||||
throw Helper.catch_to_error("Unable to create logs directory", e);
|
||||
}
|
||||
|
||||
try {
|
||||
await this._enable(sqlitePath);
|
||||
} finally {
|
||||
this.initDone.resolve(); // unblock the instance methods
|
||||
}
|
||||
@ -159,12 +170,12 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
|
||||
async setup_new_db() {
|
||||
for (const stmt of schema) {
|
||||
await this.serialize_run(stmt, []);
|
||||
await this.serialize_run(stmt);
|
||||
}
|
||||
|
||||
await this.serialize_run(
|
||||
"INSERT INTO options (name, value) VALUES ('schema_version', ?)",
|
||||
[currentSchemaVersion.toString()]
|
||||
currentSchemaVersion.toString()
|
||||
);
|
||||
}
|
||||
|
||||
@ -194,7 +205,7 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
async update_version_in_db() {
|
||||
return this.serialize_run(
|
||||
"UPDATE options SET value = ? WHERE name = 'schema_version'",
|
||||
[currentSchemaVersion.toString()]
|
||||
currentSchemaVersion.toString()
|
||||
);
|
||||
}
|
||||
|
||||
@ -206,14 +217,14 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
const to_execute = necessaryMigrations(dbVersion);
|
||||
|
||||
for (const stmt of to_execute.map((m) => m.stmts).flat()) {
|
||||
await this.serialize_run(stmt, []);
|
||||
await this.serialize_run(stmt);
|
||||
}
|
||||
|
||||
await this.update_version_in_db();
|
||||
}
|
||||
|
||||
async run_pragmas() {
|
||||
await this.serialize_run("PRAGMA foreign_keys = ON;", []);
|
||||
await this.serialize_run("PRAGMA foreign_keys = ON;");
|
||||
}
|
||||
|
||||
async run_migrations() {
|
||||
@ -225,7 +236,7 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
await this.serialize_run("BEGIN EXCLUSIVE TRANSACTION", []);
|
||||
await this.serialize_run("BEGIN EXCLUSIVE TRANSACTION");
|
||||
|
||||
try {
|
||||
if (version === 0) {
|
||||
@ -236,12 +247,16 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
|
||||
await this.insert_rollback_since(version);
|
||||
} catch (err) {
|
||||
await this.serialize_run("ROLLBACK", []);
|
||||
await this.serialize_run("ROLLBACK");
|
||||
throw err;
|
||||
}
|
||||
|
||||
await this.serialize_run("COMMIT", []);
|
||||
await this.serialize_run("VACUUM", []);
|
||||
await this.serialize_run("COMMIT");
|
||||
await this.serialize_run("VACUUM");
|
||||
}
|
||||
|
||||
async vacuum() {
|
||||
await this.serialize_run("VACUUM");
|
||||
}
|
||||
|
||||
async close() {
|
||||
@ -296,7 +311,7 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
async delete_migrations_older_than(version: number) {
|
||||
return this.serialize_run(
|
||||
"delete from migrations where migrations.version > ?",
|
||||
[version]
|
||||
version
|
||||
);
|
||||
}
|
||||
|
||||
@ -315,7 +330,7 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
|
||||
for (const rollback of _rollbacks) {
|
||||
for (const stmt of rollback.stmts) {
|
||||
await this.serialize_run(stmt, []);
|
||||
await this.serialize_run(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,18 +345,18 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
throw Error(`${version} is not a valid version to downgrade to`);
|
||||
}
|
||||
|
||||
await this.serialize_run("BEGIN EXCLUSIVE TRANSACTION", []);
|
||||
await this.serialize_run("BEGIN EXCLUSIVE TRANSACTION");
|
||||
|
||||
let new_version: number;
|
||||
|
||||
try {
|
||||
new_version = await this._downgrade_to(version);
|
||||
} catch (err) {
|
||||
await this.serialize_run("ROLLBACK", []);
|
||||
await this.serialize_run("ROLLBACK");
|
||||
throw err;
|
||||
}
|
||||
|
||||
await this.serialize_run("COMMIT", []);
|
||||
await this.serialize_run("COMMIT");
|
||||
return new_version;
|
||||
}
|
||||
|
||||
@ -369,8 +384,10 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
`insert into rollback_steps
|
||||
(migration_id, step, statement)
|
||||
values (?, ?, ?)`,
|
||||
[migration.id, step, stmt]
|
||||
);
|
||||
migration.id,
|
||||
step,
|
||||
stmt
|
||||
);
|
||||
step++;
|
||||
}
|
||||
}
|
||||
@ -401,13 +418,11 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
|
||||
await this.serialize_run(
|
||||
"INSERT INTO messages(network, channel, time, type, msg) VALUES(?, ?, ?, ?, ?)",
|
||||
[
|
||||
network.uuid,
|
||||
channel.name.toLowerCase(),
|
||||
msg.time.getTime(),
|
||||
msg.type,
|
||||
JSON.stringify(clonedMsg),
|
||||
]
|
||||
network.uuid,
|
||||
channel.name.toLowerCase(),
|
||||
msg.time.getTime(),
|
||||
msg.type,
|
||||
JSON.stringify(clonedMsg)
|
||||
);
|
||||
}
|
||||
|
||||
@ -420,7 +435,8 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
|
||||
await this.serialize_run(
|
||||
"DELETE FROM messages WHERE network = ? AND channel = ?",
|
||||
[network.uuid, channel.name.toLowerCase()]
|
||||
network.uuid,
|
||||
channel.name.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
@ -498,20 +514,47 @@ class SqliteMessageStorage implements SearchableMessageStorage {
|
||||
};
|
||||
}
|
||||
|
||||
async deleteMessages(req: DeletionRequest): Promise<number> {
|
||||
await this.initDone.promise;
|
||||
let sql = "delete from messages where id in (select id from messages where\n";
|
||||
|
||||
// We roughly get a timestamp from N days before.
|
||||
// We don't adjust for daylight savings time or other weird time jumps
|
||||
const millisecondsInDay = 24 * 60 * 60 * 1000;
|
||||
const deleteBefore = Date.now() - req.olderThanDays * millisecondsInDay;
|
||||
sql += `time <= ${deleteBefore}\n`;
|
||||
|
||||
let typeClause = "";
|
||||
|
||||
if (req.messageTypes !== null) {
|
||||
typeClause = `type in (${req.messageTypes.map((type) => `'${type}'`).join(",")})\n`;
|
||||
}
|
||||
|
||||
if (typeClause) {
|
||||
sql += `and ${typeClause}`;
|
||||
}
|
||||
|
||||
sql += "order by time asc\n";
|
||||
sql += `limit ${req.limit}\n`;
|
||||
sql += ")";
|
||||
|
||||
return this.serialize_run(sql);
|
||||
}
|
||||
|
||||
canProvideMessages() {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
private serialize_run(stmt: string, params: any[]): Promise<void> {
|
||||
private serialize_run(stmt: string, ...params: any[]): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.database.serialize(() => {
|
||||
this.database.run(stmt, params, (err) => {
|
||||
this.database.run(stmt, params, function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
resolve(this.changes); // number of affected rows, 'this' is re-bound by sqlite3
|
||||
});
|
||||
});
|
||||
});
|
||||
|
7
server/plugins/messageStorage/types.d.ts
vendored
7
server/plugins/messageStorage/types.d.ts
vendored
@ -4,6 +4,13 @@ import {Channel} from "../../models/channel";
|
||||
import {Message} from "../../models/message";
|
||||
import {Network} from "../../models/network";
|
||||
import Client from "../../client";
|
||||
import type {MessageType} from "../../models/msg";
|
||||
|
||||
export type DeletionRequest = {
|
||||
olderThanDays: number;
|
||||
messageTypes: MessageType[] | null; //null means no restriction
|
||||
limit: number; //-1 means unlimited
|
||||
}
|
||||
|
||||
interface MessageStorage {
|
||||
isEnabled: boolean;
|
||||
|
148
server/storageCleaner.ts
Normal file
148
server/storageCleaner.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import SqliteMessageStorage from "./plugins/messageStorage/sqlite";
|
||||
import {MessageType} from "./models/msg";
|
||||
import Config from "./config";
|
||||
import {DeletionRequest} from "./plugins/messageStorage/types";
|
||||
import log from "./log";
|
||||
|
||||
const status_types = [
|
||||
MessageType.AWAY,
|
||||
MessageType.BACK,
|
||||
MessageType.INVITE,
|
||||
MessageType.JOIN,
|
||||
MessageType.KICK,
|
||||
MessageType.MODE,
|
||||
MessageType.MODE_CHANNEL,
|
||||
MessageType.MODE_USER,
|
||||
MessageType.NICK,
|
||||
MessageType.PART,
|
||||
MessageType.QUIT,
|
||||
MessageType.CTCP, // not technically a status, but generally those are only of interest temporarily
|
||||
MessageType.CTCP_REQUEST,
|
||||
MessageType.CHGHOST,
|
||||
MessageType.TOPIC,
|
||||
MessageType.TOPIC_SET_BY,
|
||||
];
|
||||
|
||||
export class StorageCleaner {
|
||||
db: SqliteMessageStorage;
|
||||
olderThanDays: number;
|
||||
messageTypes: MessageType[] | null;
|
||||
limit: number;
|
||||
ticker?: ReturnType<typeof setTimeout>;
|
||||
errCount: number;
|
||||
isStopped: boolean;
|
||||
|
||||
constructor(db: SqliteMessageStorage) {
|
||||
this.errCount = 0;
|
||||
this.isStopped = true;
|
||||
this.db = db;
|
||||
this.limit = 200;
|
||||
const policy = Config.values.storagePolicy;
|
||||
this.olderThanDays = policy.maxAgeDays;
|
||||
|
||||
switch (policy.deletionPolicy) {
|
||||
case "statusOnly":
|
||||
this.messageTypes = status_types;
|
||||
break;
|
||||
case "everything":
|
||||
this.messageTypes = null;
|
||||
break;
|
||||
default:
|
||||
// exhaustive switch guard, blows up when user specifies a invalid policy enum
|
||||
this.messageTypes = assertNoBadPolicy(policy.deletionPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
private genDeletionRequest(): DeletionRequest {
|
||||
return {
|
||||
limit: this.limit,
|
||||
messageTypes: this.messageTypes,
|
||||
olderThanDays: this.olderThanDays,
|
||||
};
|
||||
}
|
||||
|
||||
async runDeletesNoLimit(): Promise<number> {
|
||||
if (!Config.values.storagePolicy.enabled) {
|
||||
// this is meant to be used by cli tools, so we guard against this
|
||||
throw new Error("storage policy is disabled");
|
||||
}
|
||||
|
||||
const req = this.genDeletionRequest();
|
||||
req.limit = -1; // unlimited
|
||||
const num_deleted = await this.db.deleteMessages(req);
|
||||
return num_deleted;
|
||||
}
|
||||
|
||||
private async runDeletes() {
|
||||
if (this.isStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.db.isEnabled) {
|
||||
// TODO: remove this once the server is intelligent enough to wait for init
|
||||
this.schedule(30 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const req = this.genDeletionRequest();
|
||||
|
||||
let num_deleted = 0;
|
||||
|
||||
try {
|
||||
num_deleted = await this.db.deleteMessages(req);
|
||||
this.errCount = 0; // reset when it works
|
||||
} catch (err: any) {
|
||||
this.errCount++;
|
||||
log.error("can't clean messages", err.message);
|
||||
|
||||
if (this.errCount === 2) {
|
||||
log.error("Cleaning failed too many times, will not retry");
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// need to recheck here as the field may have changed since the await
|
||||
if (this.isStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_deleted < req.limit) {
|
||||
this.schedule(5 * 60 * 1000);
|
||||
} else {
|
||||
this.schedule(5000); // give others a chance to execute queries
|
||||
}
|
||||
}
|
||||
|
||||
private schedule(ms: number) {
|
||||
const self = this;
|
||||
|
||||
this.ticker = setTimeout(() => {
|
||||
self.runDeletes().catch((err) => {
|
||||
log.error("storageCleaner: unexpected failure");
|
||||
throw err;
|
||||
});
|
||||
}, ms);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.isStopped = false;
|
||||
this.schedule(0);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.isStopped = true;
|
||||
|
||||
if (!this.ticker) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.ticker);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoBadPolicy(_: never): never {
|
||||
throw new Error(
|
||||
`Invalid deletion policy "${Config.values.storagePolicy.deletionPolicy}" in the \`storagePolicy\` object, fix your config.`
|
||||
);
|
||||
}
|
@ -2,8 +2,6 @@ color: true
|
||||
check-leaks: true
|
||||
recursive: true
|
||||
reporter: dot
|
||||
interactive: false
|
||||
spec: "test/**/*.ts"
|
||||
ignore: "test/client/**"
|
||||
extension: ["ts", "js"]
|
||||
require:
|
||||
|
@ -12,6 +12,7 @@ import MessageStorage, {
|
||||
rollbacks,
|
||||
} from "../../server/plugins/messageStorage/sqlite";
|
||||
import sqlite3 from "sqlite3";
|
||||
import {DeletionRequest} from "../../server/plugins/messageStorage/types"
|
||||
|
||||
const orig_schema = [
|
||||
// Schema version #1
|
||||
@ -127,6 +128,112 @@ describe("SQLite migrations", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("SQLite unit tests", function () {
|
||||
let store: MessageStorage;
|
||||
|
||||
beforeEach(async function () {
|
||||
store = new MessageStorage("testUser");
|
||||
await store._enable(":memory:");
|
||||
store.initDone.resolve();
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
await store.close();
|
||||
});
|
||||
|
||||
it("deletes messages when asked to", async function () {
|
||||
const baseDate = new Date();
|
||||
|
||||
const net = {uuid: "testnet"} as any;
|
||||
const chan = {name: "#channel"} as any;
|
||||
|
||||
for (let i = 0; i < 14; ++i) {
|
||||
await store.index(
|
||||
net,
|
||||
chan,
|
||||
new Msg({
|
||||
time: dateAddDays(baseDate, -i),
|
||||
text: `msg ${i}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const limit = 1;
|
||||
const delReq: DeletionRequest = {
|
||||
messageTypes: [MessageType.MESSAGE],
|
||||
limit: limit,
|
||||
olderThanDays: 2,
|
||||
};
|
||||
|
||||
let deleted = await store.deleteMessages(delReq);
|
||||
expect(deleted).to.equal(limit, "number of deleted messages doesn't match");
|
||||
|
||||
let id = 0;
|
||||
let messages = await store.getMessages(net, chan, () => id++);
|
||||
expect(messages.find((m) => m.text === "msg 13")).to.be.undefined; // oldest gets deleted first
|
||||
|
||||
// let's test if it properly cleans now
|
||||
delReq.limit = 100;
|
||||
deleted = await store.deleteMessages(delReq);
|
||||
expect(deleted).to.equal(11, "number of deleted messages doesn't match");
|
||||
messages = await store.getMessages(net, chan, () => id++);
|
||||
expect(messages.map((m) => m.text)).to.have.ordered.members(["msg 1", "msg 0"]);
|
||||
});
|
||||
|
||||
it("deletes only the types it should", async function () {
|
||||
const baseDate = new Date();
|
||||
|
||||
const net = {uuid: "testnet"} as any;
|
||||
const chan = {name: "#channel"} as any;
|
||||
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
await store.index(
|
||||
net,
|
||||
chan,
|
||||
new Msg({
|
||||
time: dateAddDays(baseDate, -i),
|
||||
text: `msg ${i}`,
|
||||
type: [
|
||||
MessageType.ACTION,
|
||||
MessageType.AWAY,
|
||||
MessageType.JOIN,
|
||||
MessageType.PART,
|
||||
MessageType.KICK,
|
||||
MessageType.MESSAGE,
|
||||
][i],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const delReq: DeletionRequest = {
|
||||
messageTypes: [MessageType.ACTION, MessageType.JOIN, MessageType.KICK],
|
||||
limit: 100, // effectively no limit
|
||||
olderThanDays: 0,
|
||||
};
|
||||
|
||||
let deleted = await store.deleteMessages(delReq);
|
||||
expect(deleted).to.equal(3, "number of deleted messages doesn't match");
|
||||
|
||||
let id = 0;
|
||||
let messages = await store.getMessages(net, chan, () => id++);
|
||||
expect(messages.map((m) => m.type)).to.have.ordered.members([
|
||||
MessageType.MESSAGE,
|
||||
MessageType.PART,
|
||||
MessageType.AWAY,
|
||||
]);
|
||||
|
||||
delReq.messageTypes = [
|
||||
MessageType.JOIN, // this is not in the remaining set, just here as a dummy
|
||||
MessageType.PART,
|
||||
MessageType.MESSAGE,
|
||||
];
|
||||
deleted = await store.deleteMessages(delReq);
|
||||
expect(deleted).to.equal(2, "number of deleted messages doesn't match");
|
||||
messages = await store.getMessages(net, chan, () => id++);
|
||||
expect(messages.map((m) => m.type)).to.have.ordered.members([MessageType.AWAY]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SQLite Message Storage", function () {
|
||||
// Increase timeout due to unpredictable I/O on CI services
|
||||
this.timeout(util.isRunningOnCI() ? 25000 : 5000);
|
||||
@ -388,3 +495,9 @@ describe("SQLite Message Storage", function () {
|
||||
expect(fs.existsSync(expectedPath)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
function dateAddDays(date: Date, days: number) {
|
||||
const ret = new Date(date.valueOf());
|
||||
ret.setDate(date.getDate() + days);
|
||||
return ret;
|
||||
}
|
||||
|
364
yarn.lock
364
yarn.lock
@ -23,6 +23,14 @@
|
||||
"@babel/highlight" "^7.22.13"
|
||||
chalk "^2.4.2"
|
||||
|
||||
"@babel/code-frame@^7.23.5":
|
||||
version "7.23.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
|
||||
integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.23.4"
|
||||
chalk "^2.4.2"
|
||||
|
||||
"@babel/compat-data@^7.17.10", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.9":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
|
||||
@ -80,6 +88,16 @@
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.23.6":
|
||||
version "7.23.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e"
|
||||
integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==
|
||||
dependencies:
|
||||
"@babel/types" "^7.23.6"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
|
||||
@ -242,6 +260,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
|
||||
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
|
||||
|
||||
"@babel/helper-string-parser@^7.23.4":
|
||||
version "7.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
|
||||
integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||
@ -279,11 +302,25 @@
|
||||
chalk "^2.4.2"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/highlight@^7.23.4":
|
||||
version "7.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
|
||||
integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
chalk "^2.4.2"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.17.10", "@babel/parser@^7.20.15", "@babel/parser@^7.21.3", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
|
||||
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
|
||||
|
||||
"@babel/parser@^7.23.6":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b"
|
||||
integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962"
|
||||
@ -946,6 +983,22 @@
|
||||
"@babel/parser" "^7.22.15"
|
||||
"@babel/types" "^7.22.15"
|
||||
|
||||
"@babel/traverse@7.23.6":
|
||||
version "7.23.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5"
|
||||
integrity sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.23.5"
|
||||
"@babel/generator" "^7.23.6"
|
||||
"@babel/helper-environment-visitor" "^7.22.20"
|
||||
"@babel/helper-function-name" "^7.23.0"
|
||||
"@babel/helper-hoist-variables" "^7.22.5"
|
||||
"@babel/helper-split-export-declaration" "^7.22.6"
|
||||
"@babel/parser" "^7.23.6"
|
||||
"@babel/types" "^7.23.6"
|
||||
debug "^4.3.1"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/traverse@^7.17.10", "@babel/traverse@^7.23.0":
|
||||
version "7.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
|
||||
@ -971,6 +1024,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.23.6":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002"
|
||||
integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.23.4"
|
||||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@cspotcode/source-map-consumer@0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
||||
@ -1138,21 +1200,6 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mapbox/node-pre-gyp@^1.0.0":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
|
||||
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
|
||||
dependencies:
|
||||
detect-libc "^2.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
make-dir "^3.1.0"
|
||||
node-fetch "^2.6.7"
|
||||
nopt "^5.0.0"
|
||||
npmlog "^5.0.1"
|
||||
rimraf "^3.0.2"
|
||||
semver "^7.3.5"
|
||||
tar "^6.1.11"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@ -1279,19 +1326,19 @@
|
||||
dependencies:
|
||||
eventemitter3 "^4.0.4"
|
||||
|
||||
"@textcomplete/textarea@0.1.12":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@textcomplete/textarea/-/textarea-0.1.12.tgz#7ff1a3a9c96677638e659bce8d02f80fd8c806d5"
|
||||
integrity sha512-E05H4wXr1Q50CrCFBAHewyZqvQEX681V5zleDw/31tr8vl5PDFl6TyFmS1W0jQjlrQfxa5uVvgHCx+gpfICBDQ==
|
||||
"@textcomplete/textarea@0.1.13":
|
||||
version "0.1.13"
|
||||
resolved "https://registry.yarnpkg.com/@textcomplete/textarea/-/textarea-0.1.13.tgz#a0e365877bdce1c16ec48bdd439cd44d28a903ef"
|
||||
integrity sha512-GNathnXpV361YuZrBVXvVqFYZ5NQZsjGC7Bt2sCUA/RTWlIgxHxC0ruDChYyRDx4siQZiZZOO5pWz+z1x8pZFQ==
|
||||
dependencies:
|
||||
"@textcomplete/utils" "^0.1.11"
|
||||
"@textcomplete/utils" "^0.1.13"
|
||||
textarea-caret "^3.1.0"
|
||||
undate "^0.3.0"
|
||||
|
||||
"@textcomplete/utils@^0.1.11":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@textcomplete/utils/-/utils-0.1.12.tgz#04811b4f680708579d78c60a8546c81a16440120"
|
||||
integrity sha512-llHhD1FAVwFaaHzs7PU0BZYTpNLDzTccDWbw+5cj0TiB2NOXZGjPm6l7PJrJwN/yUuPDxOHip/3I+kF6OBkBAg==
|
||||
"@textcomplete/utils@^0.1.13":
|
||||
version "0.1.13"
|
||||
resolved "https://registry.yarnpkg.com/@textcomplete/utils/-/utils-0.1.13.tgz#0b56a5a876fb27478b702e3ea118fa75960b4331"
|
||||
integrity sha512-5UW9Ee0WEX1s9K8MFffo5sfUjYm3YVhtqRhAor/ih7p0tnnpaMB7AwMRDKwhSIQL6O+g1fmEkxCeO8WqjPzjUA==
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
@ -1328,10 +1375,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@types/bcryptjs@2.4.5":
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.5.tgz#15473cc012f825b3435b189376f645bdd2fc9f11"
|
||||
integrity sha512-tOF6TivOIvq+TWQm78335CMdyVJhpBG3NUdWQDAp95ax4E2rSKbws/ELHLk5EBoucwx/tHt3/hhLOHwWJgVrSw==
|
||||
"@types/bcryptjs@2.4.6":
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz#2b92e3c2121c66eba3901e64faf8bb922ec291fa"
|
||||
integrity sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.3"
|
||||
@ -1361,10 +1408,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b"
|
||||
integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==
|
||||
|
||||
"@types/cheerio@0.22.33":
|
||||
version "0.22.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.33.tgz#e4792408b107384d7d7469e3b4d31408078ec620"
|
||||
integrity sha512-XUlu2BK4q3xJsccRLK69m/cABZd7m60o+cDEPUTG6jTpuG2vqN35UioeF99MQ/HoSOEPq0Bgil8g3jtzE0oH9A==
|
||||
"@types/cheerio@0.22.35":
|
||||
version "0.22.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.35.tgz#0d16dc1f24d426231c181b9c31847f673867595f"
|
||||
integrity sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@ -1375,10 +1422,10 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/content-disposition@0.5.7":
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.7.tgz#3b98d4bf8c80640f93b042511acb5aad18139748"
|
||||
integrity sha512-V9/5u21RHFR1zfdm3rQ6pJUKV+zSSVQt+yq16i1YhdivVzWgPEoKedc3GdT8aFjsqQbakdxuy3FnEdePUQOamQ==
|
||||
"@types/content-disposition@0.5.8":
|
||||
version "0.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537"
|
||||
integrity sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==
|
||||
|
||||
"@types/cookie@^0.4.1":
|
||||
version "0.4.1"
|
||||
@ -1448,10 +1495,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2"
|
||||
integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==
|
||||
|
||||
"@types/is-utf8@0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/is-utf8/-/is-utf8-0.2.2.tgz#b31ab599ead973992809b0b802ce066abbb42efd"
|
||||
integrity sha512-j7PFtO0ki4IoJvmMaAHQ70z74Td244dMLC7BAz5pb0v7IC8xXLtuM+7AWsMco4Minz92m30fO72+TbkmtMr4dQ==
|
||||
"@types/is-utf8@0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/is-utf8/-/is-utf8-0.2.3.tgz#4821e365f6518778bd7c9b3f8b3a5c42a8e96706"
|
||||
integrity sha512-pOsafTvuyh/FBJm+LP81graldeJLPtJ/UcfqzD+qNoey7PpG2saE/v+h8r4gxT6BGO8mzAuK2fkYLr6goOdwwg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@ -1474,10 +1521,10 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/linkify-it@3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b"
|
||||
integrity sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==
|
||||
"@types/linkify-it@3.0.5":
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8"
|
||||
integrity sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==
|
||||
|
||||
"@types/lodash@4.14.200":
|
||||
version "4.14.200"
|
||||
@ -2364,14 +2411,6 @@ archy@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
|
||||
integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==
|
||||
|
||||
are-we-there-yet@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
|
||||
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
|
||||
dependencies:
|
||||
delegates "^1.0.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
are-we-there-yet@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd"
|
||||
@ -2594,6 +2633,22 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bindings@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
||||
integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bn.js@^4.0.0:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
@ -2667,6 +2722,14 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
buffer@^5.5.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
@ -2869,6 +2932,11 @@ chokidar@3.5.3, chokidar@^3.5.3:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chownr@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
@ -2949,7 +3017,7 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-support@^1.1.2, color-support@^1.1.3:
|
||||
color-support@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
|
||||
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
|
||||
@ -2997,7 +3065,7 @@ config-chain@^1.1.13:
|
||||
ini "^1.3.4"
|
||||
proto-list "~1.2.1"
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
|
||||
console-control-strings@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
|
||||
@ -3270,7 +3338,7 @@ debug@2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@ -3519,7 +3587,7 @@ encoding@^0.1.12:
|
||||
dependencies:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
@ -3893,6 +3961,11 @@ execall@^2.0.0:
|
||||
dependencies:
|
||||
clone-regexp "^2.1.0"
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||
|
||||
express@4.17.3:
|
||||
version "4.17.3"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1"
|
||||
@ -3993,6 +4066,11 @@ file-type@16.5.4:
|
||||
strtok3 "^6.2.4"
|
||||
token-types "^4.1.1"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
|
||||
|
||||
filename-reserved-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
|
||||
@ -4124,6 +4202,11 @@ fromentries@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a"
|
||||
integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@^10.0.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
@ -4185,21 +4268,6 @@ fuzzy@0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8"
|
||||
integrity sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==
|
||||
|
||||
gauge@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
|
||||
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
|
||||
dependencies:
|
||||
aproba "^1.0.3 || ^2.0.0"
|
||||
color-support "^1.1.2"
|
||||
console-control-strings "^1.0.0"
|
||||
has-unicode "^2.0.1"
|
||||
object-assign "^4.1.1"
|
||||
signal-exit "^3.0.0"
|
||||
string-width "^4.2.3"
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
gauge@^4.0.3:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce"
|
||||
@ -4269,6 +4337,11 @@ get-symbol-description@^1.0.0:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.1"
|
||||
|
||||
github-from-package@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
@ -4636,7 +4709,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
ieee754@^1.2.1:
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@ -4690,7 +4763,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.4:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -5581,7 +5654,7 @@ minimist-options@4.1.0:
|
||||
is-plain-obj "^1.1.0"
|
||||
kind-of "^6.0.3"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5:
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
@ -5645,6 +5718,11 @@ minizlib@^2.0.0, minizlib@^2.1.1:
|
||||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
@ -5731,6 +5809,11 @@ nanoid@^3.3.6:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
|
||||
napi-build-utils@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@ -5773,22 +5856,22 @@ nise@^5.1.1:
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
node-abi@^3.3.0:
|
||||
version "3.54.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69"
|
||||
integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==
|
||||
dependencies:
|
||||
semver "^7.3.5"
|
||||
|
||||
node-abort-controller@^3.0.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||
|
||||
node-addon-api@^4.2.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
|
||||
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
|
||||
|
||||
node-fetch@^2.6.7:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
node-addon-api@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb"
|
||||
integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==
|
||||
|
||||
node-forge@1.3.0:
|
||||
version "1.3.0"
|
||||
@ -5904,16 +5987,6 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
npmlog@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
|
||||
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
|
||||
dependencies:
|
||||
are-we-there-yet "^2.0.0"
|
||||
console-control-strings "^1.1.0"
|
||||
gauge "^3.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
|
||||
npmlog@^6.0.0:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830"
|
||||
@ -5964,7 +6037,7 @@ nyc@15.1.0:
|
||||
test-exclude "^6.0.0"
|
||||
yargs "^15.0.2"
|
||||
|
||||
object-assign@^4, object-assign@^4.1.1:
|
||||
object-assign@^4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
@ -6747,6 +6820,24 @@ postcss@8.4.31, postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4.5:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
prebuild-install@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
||||
integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
|
||||
dependencies:
|
||||
detect-libc "^2.0.0"
|
||||
expand-template "^2.0.3"
|
||||
github-from-package "0.0.0"
|
||||
minimist "^1.2.3"
|
||||
mkdirp-classic "^0.5.3"
|
||||
napi-build-utils "^1.0.1"
|
||||
node-abi "^3.3.0"
|
||||
pump "^3.0.0"
|
||||
rc "^1.2.7"
|
||||
simple-get "^4.0.0"
|
||||
tar-fs "^2.0.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
precond@0.2:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
|
||||
@ -6879,7 +6970,7 @@ raw-body@2.4.3:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
rc@1.2.8, rc@^1.2.8:
|
||||
rc@1.2.8, rc@^1.2.7, rc@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||
@ -6939,7 +7030,7 @@ read@1.0.7:
|
||||
dependencies:
|
||||
mute-stream "~0.0.4"
|
||||
|
||||
readable-stream@^3.5.0, readable-stream@^3.6.0:
|
||||
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
@ -7330,11 +7421,25 @@ sigmund@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
|
||||
integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
simple-concat@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||
|
||||
simple-get@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
|
||||
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
|
||||
dependencies:
|
||||
decompress-response "^6.0.0"
|
||||
once "^1.3.1"
|
||||
simple-concat "^1.0.0"
|
||||
|
||||
sinon@13.0.2:
|
||||
version "13.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-13.0.2.tgz#c6a8ddd655dc1415bbdc5ebf0e5b287806850c3a"
|
||||
@ -7513,13 +7618,14 @@ sprintf-js@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
||||
|
||||
sqlite3@5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.6.tgz#1d4fbc90fe4fbd51e952e0a90fd8f6c2b9098e97"
|
||||
integrity sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==
|
||||
sqlite3@5.1.7:
|
||||
version "5.1.7"
|
||||
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7"
|
||||
integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp" "^1.0.0"
|
||||
node-addon-api "^4.2.0"
|
||||
bindings "^1.5.0"
|
||||
node-addon-api "^7.0.0"
|
||||
prebuild-install "^7.1.1"
|
||||
tar "^6.1.11"
|
||||
optionalDependencies:
|
||||
node-gyp "8.x"
|
||||
@ -7794,6 +7900,27 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
||||
|
||||
tar-fs@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
||||
dependencies:
|
||||
chownr "^1.1.1"
|
||||
mkdirp-classic "^0.5.2"
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-stream@^2.1.4:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
dependencies:
|
||||
bl "^4.0.3"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@^6.0.2, tar@^6.1.11, tar@^6.1.2:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73"
|
||||
@ -7881,11 +8008,6 @@ token-types@^4.1.1:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
trim-newlines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
@ -7949,6 +8071,13 @@ tsutils@^3.21.0:
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
@ -8296,11 +8425,6 @@ web-push@3.4.5:
|
||||
minimist "^1.2.5"
|
||||
urlsafe-base64 "^1.0.0"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
webpack-cli@4.9.2:
|
||||
version "4.9.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d"
|
||||
@ -8412,14 +8536,6 @@ webpack@^5:
|
||||
watchpack "^2.4.0"
|
||||
webpack-sources "^3.2.3"
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
@ -8461,7 +8577,7 @@ which@^1.2.9, which@^1.3.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.2, wide-align@^1.1.5:
|
||||
wide-align@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
||||
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
|
||||
|
Loading…
Reference in New Issue
Block a user