diff --git a/README.md b/README.md index 44967b7..fe6bd25 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,18 @@ MSS has public API with common methods and actions. You can view and test it wit Each folder contains its own README with. Here's the list of them with responsible dev and main info for each: -| Folder | Dev | What is used and how it works | -| ---------------------------- | :----------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Backend](./backend) | [@serguun42](https://github.com/serguun42) | Handles back for front, back for API, back for user accounts. All the stuff. | -| [Frontend](./frontend) | [@serguun42](https://github.com/serguun42) | Front done with Vue.js. View and save groups' schedule. | -| [Telegram bot](./telegram) | [@serguun42](https://github.com/serguun42) | Sends schedule on demand, stores users, does mailing on morning, evening and late evening. Notifies via [notifier](./notifier), uses local Telegram API server (if specified), uses local MongoDB [mirea-table-bot](https://github.com/serguun42/mirea-table-bot) was the base for it. | -| [Scrapper](./scrapper) | [@serguun42](https://github.com/serguun42) | Parses schedule page, gets links to `.xlsx`-files, parses them then, builds table models for each and every possible study group, updates DB schedule for each group of those ones. | -| [Notifier](./notifier) | [@serguun42](https://github.com/serguun42) | Runs local HTTP server, notifies into _System Telegram_, logs into _stdout_, _stderr_. Use tags (inner and passed), determines which output(s) will be used to log/notify. | -| [Panel](./panel) | [@serguun42](https://github.com/serguun42) | Configuration panel for admins to fine-tune some of the system parameters, such as semester start date and scraping interval. Requires authentication via in-house Keycloak. | -| [Healthchech](./healthcheck) | [@serguun42](https://github.com/serguun42) | Standalone healthcheck service that deploys to Yandex.Cloud Serverless containers and gives current status of the MSS. | -| [Keycloak](./keycloak) | [@serguun42](https://github.com/serguun42) | In-house instance of Keycloak to authenticate admin users for such parts of MSS as Panel. See [`docker-compose.yml`](./docker-compose.yml) for details. | -| [Android app](./app) | [@rodyapal](https://github.com/rodyapal) | Android app written in Kotlin. Serves the same task as Frontend. _Outdated, [see this page](https://mirea.xyz/apps)_. | +| Folder | Dev | What is used and how it works | +| ---------------------------- | :----------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Backend](./backend) | [@serguun42](https://github.com/serguun42) | Handles back for front, back for API, back for user accounts. All the stuff. | +| [Frontend](./frontend) | [@serguun42](https://github.com/serguun42) | Front done with Vue.js. View and save groups' schedule. | +| [Telegram bot](./telegram) | [@serguun42](https://github.com/serguun42) | Sends schedule on demand, stores users, does mailing on morning, evening and late evening. Notifies via [notifier](./notifier), uses local Telegram API server (if specified), uses local MongoDB [mirea-table-bot](https://github.com/serguun42/mirea-table-bot) was the base for it. | +| [Scrapper](./scrapper) | [@serguun42](https://github.com/serguun42) | Parses schedule page, gets links to `.xlsx`-files, parses them then, builds table models for each and every possible study group, updates DB schedule for each group of those ones. | +| [Notifier](./notifier) | [@serguun42](https://github.com/serguun42) | Runs local HTTP server, notifies into _System Telegram_, logs into _stdout_, _stderr_. Use tags (inner and passed), determines which output(s) will be used to log/notify. | +| [Panel](./panel) | [@serguun42](https://github.com/serguun42) | Configuration panel for admins to fine-tune some of the system parameters, such as semester start date and scraping interval. Requires authentication via in-house Keycloak. | +| [Monitoring](./monitoring) | [@serguun42](https://github.com/serguun42) | Monitoring done with Prometheus and Grafana. Comes with pre-built panel for monitoring Backend (see [`backend/backend-server.js`](./backend/backend-server.js#L14) and [`monitoring/grafana/node-dashboard.json`](./monitoring/grafana/node-dashboard.json)). Uses Panel as gateway/reverse-proxy with Keycloak authentication. | +| [Healthchech](./healthcheck) | [@serguun42](https://github.com/serguun42) | Standalone healthcheck service that deploys to Yandex.Cloud Serverless containers and gives current status of the MSS. | +| [Keycloak](./keycloak) | [@serguun42](https://github.com/serguun42) | In-house instance of Keycloak to authenticate admin users for such parts of MSS as Panel. See [`docker-compose.yml`](./docker-compose.yml) for details. | +| [Android app](./app) | [@rodyapal](https://github.com/rodyapal) | Android app written in Kotlin. Serves the same task as Frontend. _Outdated, [see this page](https://mirea.xyz/apps)_. | ## CI/CD diff --git a/backend/api/core.js b/backend/api/core.js new file mode 100644 index 0000000..374acd5 --- /dev/null +++ b/backend/api/core.js @@ -0,0 +1,88 @@ +import rateLimiter from "../util/rate-limiter.js"; +import logging from "../util/logging.js"; +import logTooManyRequests from "../util/log-too-many-requests.js"; +import MongoDispatcher from "../database/dispatcher.js"; +import readConfig from "../util/read-config.js"; +import listAllGroups from "./methods/list-groups-all.js"; +import getCertainGroup from "./methods/get-group.js"; +import getStartTime from "./methods/get-start-time.js"; +import getCurrentWeek from "./methods/get-week.js"; +import getStats from "./methods/get-stats.js"; + +const { DATABASE_NAME } = readConfig(); + +/** @param {import("../types").APIModuleDTO} moduleDTO */ +export default function coreAPIModule(moduleDTO) { + const { res, req, path, sendCode, sendPayload } = moduleDTO; + + moduleDTO.mongoDispatcher = new MongoDispatcher(DATABASE_NAME); + + if (rateLimiter(req)) { + logTooManyRequests(req); + return sendCode(429); + } + + res.setHeader("Access-Control-Allow-Origin", "*"); + + if (path[0] !== "api" || path[1] !== "v1.3") return sendPayload(400, { error: true, message: "No such API version" }); + + switch (path[2]) { + case "groups": + switch (path[3]) { + case "all": + listAllGroups(moduleDTO); + break; + + case "certain": + getCertainGroup(moduleDTO); + break; + + default: + sendPayload(404, { error: true, message: "No such method" }); + break; + } + break; + + case "time": + switch (path[3]) { + case "startTime": + getStartTime(moduleDTO); + break; + + case "week": + getCurrentWeek(moduleDTO); + break; + + default: + sendPayload(404, { error: true, message: "No such method" }); + break; + } + break; + + case "stats": + getStats(moduleDTO); + break; + + case "ping": + sendPayload(200, { + message: "pong" + }); + break; + + case "logs": + switch (path[3]) { + case "post": + sendCode(201); + break; + + default: + sendPayload(404, { error: true, message: "No such method" }); + break; + } + break; + + default: + sendPayload(404, { error: true, message: "No such method" }); + break; + } +} diff --git a/backend/api/methods/get-group.js b/backend/api/methods/get-group.js new file mode 100644 index 0000000..e405825 --- /dev/null +++ b/backend/api/methods/get-group.js @@ -0,0 +1,29 @@ +import logging from "../../util/logging.js"; + +/** @param {import("../../types").APIModuleDTO} moduleDTO */ +export default function getCertainGroup({ mongoDispatcher, sendCode, sendPayload, queries }) { + if (!mongoDispatcher) return sendCode(500); + + if (typeof queries["name"] !== "string") + return sendPayload(400, { error: true, message: "No required parameter" }); + + const selector = { + groupName: queries["name"] + }; + + if (queries["suffix"] && typeof queries["suffix"] === "string") selector["groupSuffix"] = queries["suffix"]; + + mongoDispatcher + .callDB() + .then((DB) => + DB.collection("study_groups").find(selector).sort({ groupName: 1, groupSuffix: 1 }).project({ _id: 0 }).toArray() + ) + .then((groups) => { + if (!groups.length) sendPayload(404, []); + else sendPayload(200, groups); + }) + .catch((e) => { + logging("Error getting certain group", e); + sendCode(500); + }); +} diff --git a/backend/api/methods/get-start-time.js b/backend/api/methods/get-start-time.js new file mode 100644 index 0000000..bd8b05b --- /dev/null +++ b/backend/api/methods/get-start-time.js @@ -0,0 +1,18 @@ +import logging from "../../util/logging.js"; + +/** @param {import("../../types").APIModuleDTO} moduleDTO */ +export default function getStartTime({ mongoDispatcher, sendCode, sendPayload }) { + if (!mongoDispatcher) return sendCode(500); + + mongoDispatcher + .callDB() + .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) + .then((found) => { + if (found && found.value) sendPayload(200, new Date(found.value).toISOString()); + else sendPayload(404, "Property start_of_weeks not found"); + }) + .catch((e) => { + logging("Error listing groups", e); + sendCode(500); + }); +} diff --git a/backend/api/methods/get-stats.js b/backend/api/methods/get-stats.js new file mode 100644 index 0000000..dc40466 --- /dev/null +++ b/backend/api/methods/get-stats.js @@ -0,0 +1,29 @@ +import logging from "../../util/logging.js"; + +/** @param {import("../../types").APIModuleDTO} moduleDTO */ +export default function getStats({ mongoDispatcher, sendCode, sendPayload }) { + if (!mongoDispatcher) return sendCode(500); + + mongoDispatcher + .callDB() + .then((DB) => + DB.collection("params") + .findOne({ name: "scrapper_updated_date" }) + .then((scrapperUpdatedDate) => { + if (scrapperUpdatedDate) + return DB.collection("study_groups") + .countDocuments() + .then((groupsCount) => { + sendPayload(200, { + scrapperUpdatedDate: new Date(scrapperUpdatedDate.value || 0).toISOString(), + groupsCount + }); + }); + else sendPayload(404, "Property scrapper_updated_date not found"); + }) + ) + .catch((e) => { + logging("Error listing groups", e); + sendCode(500); + }); +} diff --git a/backend/api/methods/get-week.js b/backend/api/methods/get-week.js new file mode 100644 index 0000000..b6d1fec --- /dev/null +++ b/backend/api/methods/get-week.js @@ -0,0 +1,22 @@ +import logging from "../../util/logging.js"; + +const SECOND = 1000; +const MINUTE = SECOND * 60; +const HOUR = MINUTE * 60; + +/** @param {import("../../types").APIModuleDTO} moduleDTO */ +export default function getCurrentWeek({ mongoDispatcher, sendCode, sendPayload }) { + if (!mongoDispatcher) return sendCode(500); + + mongoDispatcher + .callDB() + .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) + .then((found) => { + if (found && found.value) sendPayload(200, Math.ceil((Date.now() - found.value) / (7 * 24 * HOUR))); + else sendPayload(404, "Cannot compute current week"); + }) + .catch((e) => { + logging("Error listing groups", e); + sendCode(500); + }); +} diff --git a/backend/api/methods/list-groups-all.js b/backend/api/methods/list-groups-all.js new file mode 100644 index 0000000..700ef62 --- /dev/null +++ b/backend/api/methods/list-groups-all.js @@ -0,0 +1,21 @@ +import logging from "../../util/logging.js"; + +/** @param {import("../../types").APIModuleDTO} moduleDTO */ +export default function listAllGroups({ mongoDispatcher, sendCode, sendPayload }) { + if (!mongoDispatcher) return sendCode(500); + + mongoDispatcher + .callDB() + .then((DB) => + DB.collection("study_groups") + .find() + .sort({ groupName: 1, groupSuffix: 1 }) + .project({ groupName: 1, groupSuffix: 1, _id: 0 }) + .toArray() + ) + .then((names) => sendPayload(200, names)) + .catch((e) => { + logging("Error listing groups", e); + sendCode(500); + }); +} diff --git a/backend/backend-server.js b/backend/backend-server.js index cbcec48..34d8791 100644 --- a/backend/backend-server.js +++ b/backend/backend-server.js @@ -1,80 +1,95 @@ -const http = require("http"); +import { createServer, STATUS_CODES } from "node:http"; +import promClient from "prom-client"; +import { parsePath, parseQuery } from "./util/urls-and-cookies.js"; +import coreAPIModule from "./api/core.js"; +import logging from "./util/logging.js"; -/** - * @param {{[code: string]: string}} iStatusCodes - * @returns {{[code: number]: string}} - */ -const GetStatusCodes = (iStatusCodes) => { - const newCodes = {}; +const register = new promClient.Registry(); +register.setDefaultLabels({ + app: "backend-server" +}); - Object.keys(iStatusCodes).forEach((code) => (newCodes[code] = `${code} ${iStatusCodes[code]}`)); +promClient.collectDefaultMetrics({ register }); - return newCodes; -}; +const httpRequestDurationMicroseconds = new promClient.Histogram({ + name: "http_request_duration_seconds", + help: "Duration of HTTP requests in microseconds", + labelNames: ["method", "route", "code"], + buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10] +}); -/** - * HTTP Response Statuses - * @type {{[code: number]: string}} - */ -const STATUSES = GetStatusCodes(http.STATUS_CODES); +register.registerMetric(httpRequestDurationMicroseconds); -const UTIL = require("./utils/urls-and-cookies"); +export default function createBackendServer() { + return createServer((req, res) => { + const markMetrics = httpRequestDurationMicroseconds.startTimer(); -const CreateServer = () => - http.createServer((req, res) => { - const path = UTIL.ParsePath(req.url); - const queries = UTIL.ParseQuery(UTIL.SafeURL(req.url).search); - const cookies = UTIL.ParseCookie(req.headers); + const path = parsePath(req.url); + const queries = parseQuery(req.url); - res.setHeader("Content-Type", "charset=UTF-8"); + res.setHeader("Content-Type", "text/plain; charset=UTF-8"); /** - * @param {number} iCode - * @param {string | Buffer | ReadStream | Object} iData - * @returns {false} + * @param {number} code + * @param {string | Buffer | ReadStream | Object} data */ - const GlobalSendCustom = (iCode, iData) => { - res.statusCode = iCode; + const sendPayload = (code, data) => { + res.statusCode = code; - if (iData instanceof Buffer || typeof iData == "string") { - const dataToSend = iData.toString(); + if (data instanceof Buffer || typeof data == "string") { + const dataToSend = data.toString(); res.end(dataToSend); } else { - const dataToSend = JSON.stringify(iData); - res.setHeader("Content-Type", UTIL.SetCompleteMIMEType(".json")); + const dataToSend = JSON.stringify(data); + res.setHeader("Content-Type", "application/json; charset=UTF-8"); res.end(dataToSend); } - return false; + markMetrics({ + method: req.method, + route: `/${path.join("/")}`, + code + }); }; - /** - * @param {number} iCode - * @returns {false} - */ - const GlobalSend = (iCode) => { - res.statusCode = iCode || 200; - res.end(STATUSES[iCode || 500]); - return false; + /** @param {number} code */ + const sendCode = (code) => { + res.statusCode = code || 200; + res.end(`${code || 500} ${STATUS_CODES[code || 500]}`); + + markMetrics({ + method: req.method, + route: path, + code + }); }; - /** @type {import("./types").ModuleCallingObjectType} */ - const CALLING_PROPS = { + if (path[0] === "metrics") { + register + .metrics() + .then((metrics) => { + res.setHeader("Content-Type", register.contentType); + res.end(metrics); + }) + .catch((e) => { + logging(e); + sendCode(500); + }); + return; + } + + if (path[0] !== "api") return sendCode(404); + coreAPIModule({ req, res, path, queries, - cookies, - GlobalSend, - GlobalSendCustom - }; - - if (path[0] === "api") return require("./pages/api")(CALLING_PROPS); - else return GlobalSend(404); + sendCode, + sendPayload + }); }); +} -if (process.env.NODE_ENV !== "test") CreateServer().listen(80); - -module.exports = CreateServer; +if (process.env.NODE_ENV !== "test") createBackendServer().listen(80); diff --git a/backend/backend.config.json b/backend/backend.config.json index 6af4670..b23dce0 100644 --- a/backend/backend.config.json +++ b/backend/backend.config.json @@ -1,14 +1,13 @@ { - "DATABASE_NAME": "mss", - "DATABASE_CONNECTION_URI": "mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myRs&readPreference=primaryPreferred", - "LOGGING_TAG": "backend", - "LOGGING_HOST": "mss-notifier", - "LOGGING_PORT": 80, - "MAX_NUMBER_OF_BACKEND_REQUESTS_IN_MINUTE": 100, - "MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR": 500, - "BACKEND_REQUESTS_WHITELIST": [ - "10.0.0.1", - "192.168.0.1", - "255.255.256.256" - ] + "DATABASE_NAME": "mss", + "DATABASE_CONNECTION_URI": "mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myRs&readPreference=primaryPreferred", + + "LOGGING_TAG": "backend", + "LOGGING_HOST": "mss-notifier", + "LOGGING_PORT": 80, + + "MAX_NUMBER_OF_BACKEND_REQUESTS_IN_MINUTE": 100, + "MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR": 500, + + "BACKEND_REQUESTS_WHITELIST": ["10.0.0.1", "192.168.0.1", "255.255.256.256"] } diff --git a/backend/database/dispatcher.js b/backend/database/dispatcher.js new file mode 100644 index 0000000..81d7900 --- /dev/null +++ b/backend/database/dispatcher.js @@ -0,0 +1,101 @@ +import { MongoClient } from "mongodb"; +import ReadConfig from "../util/read-config.js"; +import Logging from "../util/logging.js"; + +const MONGO_CONNECTION_URL = ReadConfig().DATABASE_CONNECTION_URI || "mongodb://127.0.0.1:27017/"; + +/** + * @callback MongoDispatcherCallback + * @param {import("mongodb").Db} db + * @returns {void} + */ + +/** + * @class + * @classdesc Various events and callbacks for DB + */ +export default class MongoDispatcher { + /** + * @param {string} dbName + */ + constructor(dbName) { + /** + * @private + * @type {DB} + */ + this.DB = null; + + /** + * @private + * @type {{[eventName: string]: MongoDispatcherCallback[]}} + */ + this.events = {}; + + if (process.env.NODE_ENV !== "test") + MongoClient.connect(MONGO_CONNECTION_URL, {}) + .then((connectedClient) => { + this.DB = connectedClient.db(dbName); + + this.on("close", () => { + this.DB = null; + connectedClient.close(); + }); + }) + .catch((e) => { + Logging("Error with connection to MongoDB on start-up", e); + }); + } + + /** + * @param {string} eventName + * @param {MongoDispatcherCallback} eventHandler + * @returns {void} + */ + on(eventName, eventHandler) { + if (!this.events[eventName] || !(this.events[eventName] instanceof Array)) this.events[eventName] = []; + + this.events[eventName].push(eventHandler); + } + + /** + * @param {string} eventName + * @returns {void} + */ + off(eventName) { + delete this.events[eventName]; + } + + /** + * @param {string} eventName + * @returns {void} + */ + dispatchEvent(eventName) { + if (this.events[eventName] && this.events[eventName] instanceof Array) + this.events[eventName].forEach((eventHandler) => { + if (typeof eventHandler == "function") eventHandler(this.DB); + }); + } + + /** + * @returns {void} + */ + closeConnection() { + this.dispatchEvent("close"); + } + + /** + * @returns {Promise} + */ + callDB() { + return new Promise((resolve) => { + if (this.DB) return resolve(this.DB); + + const waitingInterval = setInterval(() => { + if (this.DB) { + clearInterval(waitingInterval); + resolve(this.DB); + } + }); + }); + } +} diff --git a/backend/jest.config.json b/backend/jest.config.json new file mode 100644 index 0000000..d86272c --- /dev/null +++ b/backend/jest.config.json @@ -0,0 +1,7 @@ +{ + "extensionsToTreatAsEsm": [], + "transform": {}, + "testMatch": [ + "**/tests/**/*" + ] +} diff --git a/backend/package-lock.json b/backend/package-lock.json index 6775875..1a1b97b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,8 +9,9 @@ "version": "1.0.0", "license": "BSL-1.0", "dependencies": { - "mongodb": "^4.1.2", - "node-fetch": "^2.7.0" + "mongodb": "^6.3.0", + "node-fetch": "^3.3.2", + "prom-client": "^15.0.0" }, "devDependencies": { "jest": "^29.7.0" @@ -29,687 +30,13 @@ "node": ">=6.0.0" } }, - "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.451.0.tgz", - "integrity": "sha512-xoImUiGoaXJZpOCgbWcdrU4vHJ8HG5KluaCkc32kuFobM277sjQimaUIHOGHL24M5vyo4QxcJD9CT/IhX63Vlg==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.451.0", - "@aws-sdk/core": "3.451.0", - "@aws-sdk/credential-provider-node": "3.451.0", - "@aws-sdk/middleware-host-header": "3.451.0", - "@aws-sdk/middleware-logger": "3.451.0", - "@aws-sdk/middleware-recursion-detection": "3.451.0", - "@aws-sdk/middleware-signing": "3.451.0", - "@aws-sdk/middleware-user-agent": "3.451.0", - "@aws-sdk/region-config-resolver": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@aws-sdk/util-endpoints": "3.451.0", - "@aws-sdk/util-user-agent-browser": "3.451.0", - "@aws-sdk/util-user-agent-node": "3.451.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz", - "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.451.0", - "@aws-sdk/middleware-host-header": "3.451.0", - "@aws-sdk/middleware-logger": "3.451.0", - "@aws-sdk/middleware-recursion-detection": "3.451.0", - "@aws-sdk/middleware-user-agent": "3.451.0", - "@aws-sdk/region-config-resolver": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@aws-sdk/util-endpoints": "3.451.0", - "@aws-sdk/util-user-agent-browser": "3.451.0", - "@aws-sdk/util-user-agent-node": "3.451.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.451.0.tgz", - "integrity": "sha512-48NcIRxWBdP1fom6RSjwn2R2u7SE7eeV3p+c4s7ukEOfrHhBxJfn3EpqBVQMGzdiU55qFImy+Fe81iA2lXq3Jw==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.451.0", - "@aws-sdk/credential-provider-node": "3.451.0", - "@aws-sdk/middleware-host-header": "3.451.0", - "@aws-sdk/middleware-logger": "3.451.0", - "@aws-sdk/middleware-recursion-detection": "3.451.0", - "@aws-sdk/middleware-sdk-sts": "3.451.0", - "@aws-sdk/middleware-signing": "3.451.0", - "@aws-sdk/middleware-user-agent": "3.451.0", - "@aws-sdk/region-config-resolver": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@aws-sdk/util-endpoints": "3.451.0", - "@aws-sdk/util-user-agent-browser": "3.451.0", - "@aws-sdk/util-user-agent-node": "3.451.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz", - "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==", - "optional": true, - "dependencies": { - "@smithy/smithy-client": "^2.1.15", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.451.0.tgz", - "integrity": "sha512-g1ZT46NuYfou00d94rJZ59N4TLI1T+v46lbHTtF9jwohiUsi7/vHkPIOdrgtrThGzGUVl01w62N0a2mpMydaBA==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz", - "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.451.0.tgz", - "integrity": "sha512-q82kEzymqimkJ2dHmuN2RGpi9HTFSxwwoXALnzPRaRcvR/v+YY8FMgSTfwXzPkHUDf/q8J+aDz6lPcYlnsP3sQ==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/util-stream": "^2.0.20", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz", - "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.451.0", - "@aws-sdk/credential-provider-process": "3.451.0", - "@aws-sdk/credential-provider-sso": "3.451.0", - "@aws-sdk/credential-provider-web-identity": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz", - "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.451.0", - "@aws-sdk/credential-provider-ini": "3.451.0", - "@aws-sdk/credential-provider-process": "3.451.0", - "@aws-sdk/credential-provider-sso": "3.451.0", - "@aws-sdk/credential-provider-web-identity": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz", - "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz", - "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.451.0", - "@aws-sdk/token-providers": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz", - "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.451.0.tgz", - "integrity": "sha512-ihbYZrI/tSVsZFDGLfJoCx3sg1s9EQqWA+xbLoquK+RjMqTnaeshYntFJmQA5yqCIbcAkyw63OwOIBRrVb7tMA==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.451.0", - "@aws-sdk/client-sso": "3.451.0", - "@aws-sdk/client-sts": "3.451.0", - "@aws-sdk/credential-provider-cognito-identity": "3.451.0", - "@aws-sdk/credential-provider-env": "3.451.0", - "@aws-sdk/credential-provider-http": "3.451.0", - "@aws-sdk/credential-provider-ini": "3.451.0", - "@aws-sdk/credential-provider-node": "3.451.0", - "@aws-sdk/credential-provider-process": "3.451.0", - "@aws-sdk/credential-provider-sso": "3.451.0", - "@aws-sdk/credential-provider-web-identity": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz", - "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz", - "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz", - "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz", - "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz", - "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.5.0", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz", - "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@aws-sdk/util-endpoints": "3.451.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz", - "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==", - "optional": true, - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz", - "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.451.0", - "@aws-sdk/middleware-logger": "3.451.0", - "@aws-sdk/middleware-recursion-detection": "3.451.0", - "@aws-sdk/middleware-user-agent": "3.451.0", - "@aws-sdk/region-config-resolver": "3.451.0", - "@aws-sdk/types": "3.451.0", - "@aws-sdk/util-endpoints": "3.451.0", - "@aws-sdk/util-user-agent-browser": "3.451.0", - "@aws-sdk/util-user-agent-node": "3.451.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz", - "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz", - "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/util-endpoints": "^1.0.4", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz", - "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/types": "^2.5.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.451.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz", - "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.451.0", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -788,30 +115,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -827,12 +154,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.3", + "@babel/types": "^7.23.5", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -956,9 +283,9 @@ } }, "node_modules/@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==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -974,32 +301,32 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -1082,9 +409,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1285,19 +612,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", "@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.3", - "@babel/types": "^7.23.3", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1306,12 +633,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -1617,642 +944,109 @@ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "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, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@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==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", - "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@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, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", - "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz", - "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==", - "optional": true, - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz", - "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==", - "optional": true, - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/property-provider": "^2.0.14", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz", - "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==", - "optional": true, - "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.5.0", - "@smithy/util-hex-encoding": "^2.0.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", - "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", - "optional": true, - "dependencies": { - "@smithy/protocol-http": "^3.0.9", - "@smithy/querystring-builder": "^2.0.13", - "@smithy/types": "^2.5.0", - "@smithy/util-base64": "^2.0.1", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz", - "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz", - "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", - "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz", - "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==", - "optional": true, - "dependencies": { - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz", - "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==", - "optional": true, - "dependencies": { - "@smithy/middleware-serde": "^2.0.13", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/shared-ini-file-loader": "^2.2.4", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz", - "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==", - "optional": true, - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/protocol-http": "^3.0.9", - "@smithy/service-error-classification": "^2.0.6", - "@smithy/types": "^2.5.0", - "@smithy/util-middleware": "^2.0.6", - "@smithy/util-retry": "^2.0.6", - "tslib": "^2.5.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz", - "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", - "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", - "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", - "optional": true, - "dependencies": { - "@smithy/property-provider": "^2.0.14", - "@smithy/shared-ini-file-loader": "^2.2.4", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", - "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", - "optional": true, - "dependencies": { - "@smithy/abort-controller": "^2.0.13", - "@smithy/protocol-http": "^3.0.9", - "@smithy/querystring-builder": "^2.0.13", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", - "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", - "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", - "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "@smithy/util-uri-escape": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz", - "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz", - "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", - "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", - "optional": true, - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz", - "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==", - "optional": true, - "dependencies": { - "@smithy/eventstream-codec": "^2.0.13", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/types": "^2.5.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-middleware": "^2.0.6", - "@smithy/util-uri-escape": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", - "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", - "optional": true, - "dependencies": { - "@smithy/middleware-stack": "^2.0.7", - "@smithy/types": "^2.5.0", - "@smithy/util-stream": "^2.0.20", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", - "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz", - "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==", - "optional": true, - "dependencies": { - "@smithy/querystring-parser": "^2.0.13", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", - "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", - "optional": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", - "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", - "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", - "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", - "optional": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", - "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=14.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz", - "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==", - "optional": true, + "node_modules/@jridgewell/gen-mapping": { + "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, "dependencies": { - "@smithy/property-provider": "^2.0.14", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { - "node": ">= 10.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz", - "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==", - "optional": true, - "dependencies": { - "@smithy/config-resolver": "^2.0.18", - "@smithy/credential-provider-imds": "^2.1.1", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/property-provider": "^2.0.14", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, "engines": { - "node": ">= 10.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/util-endpoints": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz", - "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==", - "optional": true, - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, + "node_modules/@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==", + "dev": true, "engines": { - "node": ">= 14.0.0" + "node": ">=6.0.0" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", - "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, - "node_modules/@smithy/util-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", - "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", - "optional": true, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@smithy/util-retry": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz", - "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==", - "optional": true, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", "dependencies": { - "@smithy/service-error-classification": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" + "sparse-bitfield": "^3.0.3" } }, - "node_modules/@smithy/util-stream": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", - "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", - "optional": true, - "dependencies": { - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/types": "^2.5.0", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, + "node_modules/@opentelemetry/api": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz", + "integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==", "engines": { - "node": ">=14.0.0" + "node": ">=8.0.0" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", - "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", - "optional": true, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@smithy/util-utf8": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", - "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", - "optional": true, + "node_modules/@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, "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@types/babel__core": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz", - "integrity": "sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -2324,9 +1118,10 @@ } }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -2343,18 +1138,17 @@ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.3.tgz", + "integrity": "sha512-z1ELvMijRL1QmU7QuzDkeYXSF2+dXI0ITKoQsIoVKcNBOiK5RMmWy+pYYxJTHFt8vkpZe7UsvRErQwcxZkjoUw==", "dependencies": { - "@types/node": "*", "@types/webidl-conversions": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -2540,30 +1334,10 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "optional": true + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -2588,9 +1362,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "dev": true, "funding": [ { @@ -2607,9 +1381,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -2629,37 +1403,11 @@ } }, "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "node": ">=16.20.1" } }, "node_modules/buffer-from": { @@ -2687,9 +1435,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001562", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", - "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", + "version": "1.0.30001568", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", + "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", "dev": true, "funding": [ { @@ -2847,6 +1595,14 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2906,9 +1662,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.586", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.586.tgz", - "integrity": "sha512-qMa+E6yf1fNQbg3G66pHLXeJUP5CCCzNat1VPczOZOqgI2w4u+8y9sQnswMdGs5m4C1rOePq37EVBr/nsPQY7w==", + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==", "dev": true }, "node_modules/emittery": { @@ -3023,35 +1779,35 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" }, { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "type": "paypal", + "url": "https://paypal.me/jimmywarting" } ], - "optional": true, "dependencies": { - "strnum": "^1.0.5" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" + "engines": { + "node": "^12.20 || >= 14.13" } }, "node_modules/fill-range": { @@ -3079,6 +1835,17 @@ "node": ">=8" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3218,25 +1985,6 @@ "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -3281,11 +2029,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4190,8 +2933,7 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -4234,29 +2976,57 @@ } }, "node_modules/mongodb": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.1.tgz", - "integrity": "sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", + "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^3.0.0" }, "engines": { - "node": ">=12.9.0" + "node": ">=16.20.1" }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "@mongodb-js/saslprep": "^1.1.0" + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", + "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" } }, "node_modules/ms": { @@ -4271,42 +3041,39 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-int64": { @@ -4316,9 +3083,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -4533,6 +3300,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prom-client": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.0.0.tgz", + "integrity": "sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA==", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4683,28 +3462,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4728,7 +3485,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, "dependencies": { "memory-pager": "^1.0.2" } @@ -4820,12 +3576,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "optional": true - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4850,6 +3600,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -4892,22 +3650,16 @@ } }, "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "optional": true - }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4932,7 +3684,8 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/update-browserslist-db": { "version": "1.0.13", @@ -4964,19 +3717,10 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -4996,6 +3740,14 @@ "makeerror": "1.0.12" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -5005,15 +3757,15 @@ } }, "node_modules/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==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", "dependencies": { - "tr46": "^3.0.0", + "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=16" } }, "node_modules/which": { diff --git a/backend/package.json b/backend/package.json index 0bab0d8..1f6dcc7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,15 +3,17 @@ "version": "1.0.0", "description": "", "main": "backend-server.js", + "type": "module", "scripts": { - "test": "jest ./tests/*", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "production": "node backend-server.js" }, "author": "serguun42", "license": "BSL-1.0", "dependencies": { - "mongodb": "^4.1.2", - "node-fetch": "^2.7.0" + "mongodb": "^6.3.0", + "node-fetch": "^3.3.2", + "prom-client": "^15.0.0" }, "devDependencies": { "jest": "^29.7.0" diff --git a/backend/pages/api.js b/backend/pages/api.js deleted file mode 100644 index 4845be5..0000000 --- a/backend/pages/api.js +++ /dev/null @@ -1,425 +0,0 @@ -const - SECOND = 1e3, - MINUTE = SECOND * 60, - HOUR = MINUTE * 60, - DEV = require("os").platform() === "win32" || process.argv[2] === "DEV", - { DATABASE_NAME } = DEV ? require("../../../DEV_CONFIGS/backend.config.json") : require("../backend.config.json"), - RateLimiter = require("../utils/rate-limiter"), - MongoDispatcher = require("../utils/database"), - Logging = require("../utils/logging"); - -/** - * Connections from __*single IP*__ - * @type {{[ip: string]: number}} - * */ -const RECENTLY_LOGGED_IPS = {}; - -/** - * @param {import("http").IncomingMessage} req - * @returns {void} - */ -const LogTooManyRequests = (req) => { - const cIP = req.headers?.["x-real-ip"] || req.socket?.remoteAddress; - - if (!cIP) return; - - if (!RECENTLY_LOGGED_IPS[cIP]) - RECENTLY_LOGGED_IPS[cIP] = 1; - else - ++RECENTLY_LOGGED_IPS[cIP]; - - setTimeout(() => --RECENTLY_LOGGED_IPS[cIP], MINUTE * 5); - - if (RECENTLY_LOGGED_IPS[cIP] > 1) return; - - Logging(`Too many requests from ${typeof cIP === "string" ? cIP.replace("::ffff:", "") : cIP} to ${req.url}`); -}; - - -const mongoDispatcher = new MongoDispatcher(DATABASE_NAME); - - -/** - * @param {import("../types").ModuleCallingObjectType} iModuleDataObject - */ -module.exports = (iModuleDataObject) => { - const { res, req, queries, path, GlobalSend, GlobalSendCustom } = iModuleDataObject; - - if (RateLimiter(req)) { - LogTooManyRequests(req); - return GlobalSend(429); - }; - - - res.setHeader("Access-Control-Allow-Origin", "*"); - - if (path[0] !== "api") return GlobalSendCustom(400, {error: true, message: "No such version"}); - - if (path[1] === "v1") { - switch (path[2]) { - case "groups": - if (queries["getAll"]) { - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find() - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ groupName: 1, groupSuffix: 1, _id: 0 }) - .toArray() - ) - .then((names) => GlobalSendCustom(200, names)) - .catch(Logging); - } else if (queries["get"]) { - const selector = { - groupName: queries["get"] - }; - - if (queries["suffix"] && typeof queries["suffix"] === "string") - selector["groupSuffix"] = queries["suffix"]; - - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find(selector) - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ _id: 0 }) - .toArray() - ) - .then((groups) => { - if (!groups.length) - GlobalSendCustom(404, []); - else - GlobalSendCustom(200, groups); - }) - .catch(Logging); - } else - GlobalSendCustom(400, {error: true, message: "No such action"}); - break; - - case "time": - switch (path[3]) { - case "startTime": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, `${found.value}`); - else - GlobalSendCustom(404, "Property start_of_weeks not found"); - }) - .catch(Logging); - break; - - case "week": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, Math.ceil((Date.now() - found.value) / (7 * 24 * HOUR))); - else - GlobalSendCustom(404, "Cannot compute current week"); - }) - .catch(Logging); - break; - - case "currentDay": - GlobalSendCustom(200, new Date(Date.now() + (!DEV) * 3 * HOUR).getDay()); - break; - - default: GlobalSendCustom(400, {error: true, message: "No such method"}); break; - } - break; - - default: GlobalSendCustom(400, {error: true, message: "No such method"}); break; - } - } else if (path[1] === "v1.1") { - switch (path[2]) { - case "groups": - switch (path[3]) { - case "all": - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find() - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ groupName: 1, groupSuffix: 1, _id: 0 }) - .toArray() - ) - .then((names) => GlobalSendCustom(200, names)) - .catch(Logging); - break; - - case "certain": - if (typeof queries["name"] !== "string") - return GlobalSendCustom(400, {error: true, message: "No required parameter"}); - - const selector = { - groupName: queries["name"] - }; - - if (queries["suffix"] && typeof queries["suffix"] === "string") - selector["groupSuffix"] = queries["suffix"]; - - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find(selector) - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ _id: 0 }) - .toArray() - ) - .then((groups) => { - if (!groups.length) - GlobalSendCustom(404, []); - else - GlobalSendCustom(200, groups); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - case "time": - switch (path[3]) { - case "startTime": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, `${found.value}`); - else - GlobalSendCustom(404, "Property start_of_weeks not found"); - }) - .catch(Logging); - break; - - case "week": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, Math.ceil((Date.now() - found.value) / (7 * 24 * HOUR))); - else - GlobalSendCustom(404, "Cannot compute current week"); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - } else if (path[1] === "v1.2") { - switch (path[2]) { - case "groups": - switch (path[3]) { - case "all": - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find() - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ groupName: 1, groupSuffix: 1, _id: 0 }) - .toArray() - ) - .then((names) => GlobalSendCustom(200, names)) - .catch(Logging); - break; - - case "certain": - if (typeof queries["name"] !== "string") - return GlobalSendCustom(400, {error: true, message: "No required parameter"}); - - const selector = { - groupName: queries["name"] - }; - - if (queries["suffix"] && typeof queries["suffix"] === "string") - selector["groupSuffix"] = queries["suffix"]; - - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find(selector) - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ _id: 0 }) - .toArray() - ) - .then((groups) => { - if (!groups.length) - GlobalSendCustom(404, []); - else - GlobalSendCustom(200, groups); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - case "time": - switch (path[3]) { - case "startTime": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, `${found.value}`); - else - GlobalSendCustom(404, "Property start_of_weeks not found"); - }) - .catch(Logging); - break; - - case "week": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, Math.ceil((Date.now() - found.value) / (7 * 24 * HOUR))); - else - GlobalSendCustom(404, "Cannot compute current week"); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - case "logs": - switch (path[3]) { - case "post": - GlobalSend(201); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - } else if (path[1] === "v1.3") { - switch (path[2]) { - case "groups": - switch (path[3]) { - case "all": - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find() - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ groupName: 1, groupSuffix: 1, _id: 0 }) - .toArray() - ) - .then((names) => GlobalSendCustom(200, names)) - .catch(Logging); - break; - - case "certain": - if (typeof queries["name"] !== "string") - return GlobalSendCustom(400, {error: true, message: "No required parameter"}); - - const selector = { - groupName: queries["name"] - }; - - if (queries["suffix"] && typeof queries["suffix"] === "string") - selector["groupSuffix"] = queries["suffix"]; - - mongoDispatcher.callDB() - .then((DB) => - DB.collection("study_groups") - .find(selector) - .sort({ groupName: 1, groupSuffix: 1 }) - .project({ _id: 0 }) - .toArray() - ) - .then((groups) => { - if (!groups.length) - GlobalSendCustom(404, []); - else - GlobalSendCustom(200, groups); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - case "time": - switch (path[3]) { - case "startTime": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, new Date(found.value).toISOString()); - else - GlobalSendCustom(404, "Property start_of_weeks not found"); - }) - .catch(Logging); - break; - - case "week": - mongoDispatcher.callDB() - .then((DB) => DB.collection("params").findOne({ name: "start_of_weeks" })) - .then((found) => { - if (found && found.value) - GlobalSendCustom(200, Math.ceil((Date.now() - found.value) / (7 * 24 * HOUR))); - else - GlobalSendCustom(404, "Cannot compute current week"); - }) - .catch(Logging); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - case "stats": - mongoDispatcher.callDB() - .then((DB) => - DB.collection("params").findOne({ name: "scrapper_updated_date" }) - .then((scrapperUpdatedDate) => { - if (scrapperUpdatedDate) - return DB.collection("study_groups").countDocuments().then((groupsCount) => { - GlobalSendCustom(200, { - scrapperUpdatedDate: new Date(scrapperUpdatedDate.value || 0).toISOString(), - groupsCount - }); - }); - else - GlobalSendCustom(404, "Property scrapper_updated_date not found"); - }) - ) - .catch(Logging); - break; - - case "ping": - GlobalSendCustom(200, { - message: "pong" - }); - break; - - case "logs": - switch (path[3]) { - case "post": - GlobalSend(201); - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - break; - - default: GlobalSendCustom(404, {error: true, message: "No such method"}); break; - } - } else - return GlobalSendCustom(400, {error: true, message: "No such API version"}); -}; diff --git a/backend/tests/integration.test.js b/backend/tests/integration.test.js index 6bf8ffb..5848c05 100644 --- a/backend/tests/integration.test.js +++ b/backend/tests/integration.test.js @@ -1,27 +1,20 @@ -const { default: fetch } = require("node-fetch"); -const CreateServer = require("../backend-server.js"); +import { jest } from "@jest/globals"; +import createBackendServer from "../backend-server.js"; -const Wait = (delay = 1000) => - new Promise((resolve) => { - setTimeout(() => resolve(), delay); - }); +jest.useFakeTimers(); describe("Whole Backend", () => { /** @type {import('http').Server} */ let server; beforeAll(() => { - server = CreateServer().listen(15077); + server = createBackendServer().listen(15077); }); beforeEach(() => { jest.setTimeout(2000); }); - afterAll(async () => { - return Wait(1000).then(() => server.close()); - }, 2000); - test("Server is up", async () => { const statusCode = await fetch("http://localhost:15077").then((res) => res.status); @@ -37,4 +30,8 @@ describe("Whole Backend", () => { return Promise.resolve(); }); + + afterAll(() => { + server.close(); + }); }); diff --git a/backend/tests/limits.test.js b/backend/tests/limits.test.js index 11a95b7..33c0746 100644 --- a/backend/tests/limits.test.js +++ b/backend/tests/limits.test.js @@ -1,8 +1,9 @@ -const RateLimiter = require("../utils/rate-limiter.js"); +import { jest } from "@jest/globals"; +import rateLimiter from "../util/rate-limiter.js"; describe("Rate limiting", () => { test("Rate limiting", () => { - rateLimitCheck = jest.fn(RateLimiter); + rateLimitCheck = jest.fn(rateLimiter); const MOCKING_DATA = { socket: { remoteAddress: "1.2.3.4" } }; const MOCKING_TIMES = 1000; diff --git a/backend/tests/urls.test.js b/backend/tests/urls.test.js index 6f0dfe9..f5ce988 100644 --- a/backend/tests/urls.test.js +++ b/backend/tests/urls.test.js @@ -1,17 +1,17 @@ -const { ParsePath, ParseQuery } = require("../utils/urls-and-cookies.js"); +import { parsePath, parseQuery } from "../util/urls-and-cookies.js"; describe("URLs", () => { test("Basic pathname parsing", () => { - expect(ParsePath("/api/v1/")).toEqual(["api", "v1"]); - expect(ParsePath("")).toEqual([]); + expect(parsePath("/api/v1/")).toEqual(["api", "v1"]); + expect(parsePath("")).toEqual([]); }); test("Malformed pathname parsing", () => { - expect(ParsePath("..////api/v1/../..%2F")).toEqual(["api", "v1"]); + expect(parsePath("..////api/v1/../..%2F")).toEqual(["api", "v1"]); }); test("Search params testing", () => { - expect(ParseQuery("?one=two")).toEqual({ one: "two" }); - expect(ParseQuery("?more")).toEqual({ more: true }); + expect(parseQuery("?one=two")).toEqual({ one: "two" }); + expect(parseQuery("?more")).toEqual({ more: true }); }); }); diff --git a/backend/types/index.d.ts b/backend/types/index.d.ts index 2e53341..111ffb7 100644 --- a/backend/types/index.d.ts +++ b/backend/types/index.d.ts @@ -1,42 +1,24 @@ -export type ModuleCallingObjectType = { - req: import("http").IncomingMessage; - res: import("http").ServerResponse; - path: string[]; - queries: { - [queryName: string]: string | true; - }; - cookies: { - [name: string]: string; - }; - GlobalSend: (iCode: number) => void; - GlobalSendCustom: (iCode: number, iData: any) => void; -}; - -export type UtilsType = { - /** Example `UTIL.SafeDecode(UTIL.SafeURL(req.url).pathname)` */ - SafeDecode: (iString: string) => string; - - /** Example `UTIL.SafeEscape(pathname)` */ - SafeEscape: (iString: string) => string; - - /** Example `UTIL.SafeURL(req.url)` */ - SafeURL: (iPathname: string) => URL; - - /** Example `UTIL.ParseCookie(req.headers)` */ - ParseCookie: (iHeaders: {[headerName: string]: string}) => {[name: string]: string}; - - /** Example `UTIL.ParsePath(req.url)` */ - ParsePath: (iPath: string) => string[]; +export type BackendConfig = { + DATABASE_NAME: string; + DATABASE_CONNECTION_URI: string; - /** Example `UTIL.ParseQuery(UTIL.SafeURL(req.url).search)` */ - ParseQuery: (iQuery: string) => {[queryName: string]: string | true}; + LOGGING_TAG: string; + LOGGING_HOST: string; + LOGGING_PORT: number; - /** Example `UTIL.CombineQueries({ pass: true, search: "Seeking group" })` */ - CombineQueries: (iQueries: {[queryName: string]: string | true}) => string; - - SetMIMEType: (iExtention: string) => string; - - SetCompleteMIMEType: (iExtention: string) => string; -} + MAX_NUMBER_OF_BACKEND_REQUESTS_IN_MINUTE: number; + MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR: number; + BACKEND_REQUESTS_WHITELIST: string[]; +}; -export default ModuleCallingObjectType; +export type APIModuleDTO = { + req: import("http").IncomingMessage; + res: import("http").ServerResponse; + path: string[]; + queries: { + [queryName: string]: string | true; + }; + sendCode: (code: number) => void; + sendPayload: (code: number, data: any) => void; + mongoDispatcher?: import("../database/dispatcher").default; +}; diff --git a/backend/util/is-dev.js b/backend/util/is-dev.js new file mode 100644 index 0000000..b2da7b2 --- /dev/null +++ b/backend/util/is-dev.js @@ -0,0 +1,3 @@ +const IS_DEV = process.env.NODE_ENV === "development"; + +export default IS_DEV; diff --git a/backend/util/log-too-many-requests.js b/backend/util/log-too-many-requests.js new file mode 100644 index 0000000..3d783a2 --- /dev/null +++ b/backend/util/log-too-many-requests.js @@ -0,0 +1,26 @@ +import logging from "./logging.js"; + +/** + * Connections from single IP + * @type {{[ip: string]: number}} + */ +const RECENTLY_LOGGED_IPS = {}; + +/** + * @param {import("http").IncomingMessage} req + * @returns {void} + */ +export default function logTooManyRequests(req) { + const cIP = req.headers?.["x-real-ip"] || req.socket?.remoteAddress; + + if (!cIP) return; + + if (!RECENTLY_LOGGED_IPS[cIP]) RECENTLY_LOGGED_IPS[cIP] = 1; + else ++RECENTLY_LOGGED_IPS[cIP]; + + setTimeout(() => --RECENTLY_LOGGED_IPS[cIP], MINUTE * 5); + + if (RECENTLY_LOGGED_IPS[cIP] > 1) return; + + logging(`Too many requests from ${typeof cIP === "string" ? cIP.replace("::ffff:", "") : cIP} to ${req.url}`); +} diff --git a/backend/utils/logging.js b/backend/util/logging.js similarity index 67% rename from backend/utils/logging.js rename to backend/util/logging.js index 6e6eb3d..4a8f84c 100644 --- a/backend/utils/logging.js +++ b/backend/util/logging.js @@ -1,15 +1,16 @@ -const fetch = require("node-fetch"); +import fetch from "node-fetch"; +import readConfig from "./read-config.js"; +import IS_DEV from "./is-dev.js"; -const DEV = require("os").platform() === "win32" || process.argv[2] === "DEV"; -const { LOGGING_HOST, LOGGING_PORT, LOGGING_TAG } = DEV - ? require("../../../DEV_CONFIGS/backend.config.json") - : require("../backend.config.json"); +const { LOGGING_HOST, LOGGING_PORT, LOGGING_TAG } = readConfig(); /** * @param {(Error | String)[]} args * @returns {void} */ -const Logging = (...args) => { +export default function logging(...args) { + if (IS_DEV) return console.log(...args); + const payload = { error: args.findIndex((message) => message instanceof Error) > -1, args: args.map((arg) => (arg instanceof Error ? { ERROR_name: arg.name, ERROR_message: arg.message } : arg)), @@ -32,6 +33,4 @@ const Logging = (...args) => { console.warn(new Date()); console.warn(e); }); -}; - -module.exports = DEV ? console.log : Logging; +} diff --git a/backend/utils/rate-limiter.js b/backend/util/rate-limiter.js similarity index 73% rename from backend/utils/rate-limiter.js rename to backend/util/rate-limiter.js index 928f598..271bac2 100644 --- a/backend/utils/rate-limiter.js +++ b/backend/util/rate-limiter.js @@ -1,10 +1,11 @@ -const SECOND = 1e3, - MINUTE = SECOND * 60, - HOUR = MINUTE * 60, - DEV = require("os").platform() === "win32" || process.argv[2] === "DEV", - { MAX_NUMBER_OF_BACKEND_REQUESTS_IN_MINUTE, MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR, BACKEND_REQUESTS_WHITELIST } = DEV - ? require("../../../DEV_CONFIGS/backend.config.json") - : require("../backend.config.json"); +import readConfig from "./read-config.js"; + +const SECOND = 1e3; +const MINUTE = SECOND * 60; +const HOUR = MINUTE * 60; + +const { MAX_NUMBER_OF_BACKEND_REQUESTS_IN_MINUTE, MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR, BACKEND_REQUESTS_WHITELIST } = + readConfig(); /** * Connections from __*single IP*__ within __*last minute*__ @@ -25,7 +26,7 @@ const HOUR_IPS = new Object(); * @param {import("http").IncomingMessage} req * @returns {boolean} */ -module.exports = (req) => { +export default function rateLimiter(req) { const cIP = req.headers?.["x-real-ip"] || req.socket?.remoteAddress; if (!cIP) return true; @@ -47,4 +48,4 @@ module.exports = (req) => { if (HOUR_IPS[cIP] > MAX_NUMBER_OF_BACKEND_REQUESTS_IN_HOUR) return true; return false; -}; +} diff --git a/backend/util/read-config.js b/backend/util/read-config.js new file mode 100644 index 0000000..6d40867 --- /dev/null +++ b/backend/util/read-config.js @@ -0,0 +1,23 @@ +import { readFileSync } from "node:fs"; +import IS_DEV from "./is-dev.js"; + +/** @type {{ lastReadValue?: string }} */ +const CONFIG_HOT_STORAGE = {}; + +/** + * Read config based on ENV + * + * @returns {import('../types/index.js').BackendConfig} + */ +export default function readConfig() { + const configFileLocation = (IS_DEV && process.env.CONFIG_LOCATION) || "./backend.config.json"; + + try { + if (!CONFIG_HOT_STORAGE.lastReadValue) + CONFIG_HOT_STORAGE.lastReadValue = readFileSync(configFileLocation).toString(); + return JSON.parse(CONFIG_HOT_STORAGE.lastReadValue); + } catch (e) { + console.error(e); + process.exit(1); + } +} diff --git a/backend/util/urls-and-cookies.js b/backend/util/urls-and-cookies.js new file mode 100644 index 0000000..fc1c0b0 --- /dev/null +++ b/backend/util/urls-and-cookies.js @@ -0,0 +1,65 @@ +/** + * @param {string} encoded + * @returns {string} + */ +const safeDecode = (encoded) => { + if (typeof encoded !== "string") return encoded; + + try { + const decoded = decodeURIComponent(encoded); + return decoded + .replace(/(\/+)/gi, "/") + .replace(/\.\.\%2F/gi, "") + .replace(/\.\.\//g, ""); + } catch (e) { + return encoded + .replace(/(\/+)/gi, "/") + .replace(/\.\.\%2F/gi, "") + .replace(/\.\.\//g, ""); + } +}; + +/** + * @param {string} pathname + * @param {string} [origin] + * @returns {URL} + */ +const buildSafeURL = (pathname, origin) => { + if (typeof origin !== "string") origin = "https://example.com"; + + if (typeof pathname !== "string") return new URL("/", origin); + + try { + return new URL(safeDecode(pathname), origin); + } catch (e) { + return new URL("/", origin); + } +}; + +/** + * @param {string} requestedURL + * @returns {string[]} + */ +export const parsePath = (requestedURL) => { + const safePathname = buildSafeURL(requestedURL, "https://mirea.xyz").pathname; + + return safePathname.split("/").filter(Boolean); +}; + +/** + * @param {string} requestedURL + * @returns {{[queryName: string]: string}} + */ +export const parseQuery = (requestedURL) => { + if (!requestedURL) return {}; + + /** @type {{[queryName: string]: string}} */ + const queries = {}; + const safeSearchParams = buildSafeURL(requestedURL, "https://mirea.xyz").searchParams; + + Array.from(safeSearchParams.entries()).forEach(([queryName, queryValue]) => { + queries[queryName] = queryValue || true; + }); + + return queries; +}; diff --git a/backend/utils/database.d.ts b/backend/utils/database.d.ts deleted file mode 100644 index c8e2e40..0000000 --- a/backend/utils/database.d.ts +++ /dev/null @@ -1,58 +0,0 @@ -export = MongoDispatcher; -/** - * @typedef {import("mongodb").Db} DB - */ -/** - * @callback MongoDispatcherCallback - * @param {DB} iDB - * @returns {void} - */ -/** - * @class - * @classdesc Various events and callbacks for DB - */ -declare class MongoDispatcher { - /** - * @param {string} iDatabaseName - */ - constructor(iDatabaseName: string); - /** - * @private - * @type {DB} - */ - private DB; - /** - * @private - * @type {{[eventName: string]: MongoDispatcherCallback[]}} - */ - private events; - /** - * @param {string} iEventName - * @param {MongoDispatcherCallback} iOnEventHandler - * @returns {void} - */ - on(iEventName: string, iOnEventHandler: MongoDispatcherCallback): void; - /** - * @param {string} iEventName - * @returns {void} - */ - off(iEventName: string): void; - /** - * @param {string} iEventName - * @returns {void} - */ - dispatchEvent(iEventName: string): void; - /** - * @returns {void} - */ - closeConnection(): void; - /** - * @returns {Promise} - */ - callDB(): Promise; -} -declare namespace MongoDispatcher { - export { DB, MongoDispatcherCallback }; -} -type MongoDispatcherCallback = (iDB: import("mongodb").Db) => void; -type DB = import("mongodb").Db; diff --git a/backend/utils/database.js b/backend/utils/database.js deleted file mode 100644 index 5a4e9de..0000000 --- a/backend/utils/database.js +++ /dev/null @@ -1,108 +0,0 @@ -const Logging = require("./logging"); -const DEV = require("os").platform() === "win32" || process.argv[2] === "DEV"; -const { DATABASE_CONNECTION_URI } = DEV - ? require("../../../DEV_CONFIGS/backend.config.json") - : require("../backend.config.json"); -const mongoClient = require("mongodb").MongoClient; -const MONGO_CONNECTION_OPTIONS = {}; -const MONGO_URL = DATABASE_CONNECTION_URI || "mongodb://127.0.0.1:27017/"; - -/** - * @typedef {import("mongodb").Db} DB - */ -/** - * @callback MongoDispatcherCallback - * @param {DB} iDB - * @returns {void} - */ -/** - * @class - * @classdesc Various events and callbacks for DB - */ -class MongoDispatcher { - /** - * @param {string} iDatabaseName - */ - constructor(iDatabaseName) { - /** - * @private - * @type {DB} - */ - this.DB = null; - - /** - * @private - * @type {{[eventName: string]: MongoDispatcherCallback[]}} - */ - this.events = {}; - - if (process.env.NODE_ENV !== "test") - mongoClient.connect(MONGO_URL, MONGO_CONNECTION_OPTIONS, (mongoError, mongoConnection) => { - if (mongoError) { - Logging("Error with connection to MongoDB on start-up", mongoError); - } else { - this.DB = mongoConnection.db(iDatabaseName); - - this.on("close", () => { - this.DB = null; - mongoConnection.close(); - }); - } - }); - } - - /** - * @param {string} iEventName - * @param {MongoDispatcherCallback} iOnEventHandler - * @returns {void} - */ - on(iEventName, iOnEventHandler) { - if (!this.events[iEventName] || !(this.events[iEventName] instanceof Array)) this.events[iEventName] = []; - - this.events[iEventName].push(iOnEventHandler); - } - - /** - * @param {string} iEventName - * @returns {void} - */ - off(iEventName) { - delete this.events[iEventName]; - } - - /** - * @param {string} iEventName - * @returns {void} - */ - dispatchEvent(iEventName) { - if (this.events[iEventName] && this.events[iEventName] instanceof Array) - this.events[iEventName].forEach((eventHandler) => { - if (typeof eventHandler == "function") eventHandler(this.DB); - }); - } - - /** - * @returns {void} - */ - closeConnection() { - this.dispatchEvent("close"); - } - - /** - * @returns {Promise} - */ - callDB() { - return new Promise((resolve) => { - if (this.DB) return resolve(this.DB); - - const waitingInterval = setInterval(() => { - if (this.DB) { - clearInterval(waitingInterval); - resolve(this.DB); - } - }); - }); - } -} - -module.exports = MongoDispatcher; diff --git a/backend/utils/logging.d.ts b/backend/utils/logging.d.ts deleted file mode 100644 index cc8bc9e..0000000 --- a/backend/utils/logging.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export = Logging; -/** - * @param {(Error | String)[]} args - * @returns {void} - */ -declare function Logging(...args: (Error | string)[]): void; diff --git a/backend/utils/urls-and-cookies.js b/backend/utils/urls-and-cookies.js deleted file mode 100644 index 3a3033f..0000000 --- a/backend/utils/urls-and-cookies.js +++ /dev/null @@ -1,197 +0,0 @@ -/** - * MIME-Type accordance for different file formats - * @type {{format: string, type: string, noCharset?: boolean}[]} - */ -const TYPES_ACCORDANCE = [ - { format: "mp4", type: "video/mp4", noCharset: true }, - { format: "mpeg", type: "video/mpeg", noCharset: true }, - { format: "mkv", type: "video/x-matroska", noCharset: true }, - { format: "avi", type: "video/x-msvideo", noCharset: true }, - { format: "mp3", type: "audio/mp3", noCharset: true }, - { format: "aac", type: "audio/aac", noCharset: true }, - { format: "ac3", type: "audio/ac3", noCharset: true }, - { format: "wav", type: "audio/wav", noCharset: true }, - { format: "flac", type: "audio/flac", noCharset: true }, - { format: "ape", type: "audio/x-monkeys-audio", noCharset: true }, - { format: "txt", type: "text/plain" }, - { format: "html", type: "text/html" }, - { format: "js", type: "application/javascript" }, - { format: "vue", type: "application/javascript" }, - { format: "json", type: "application/json" }, - { format: "webmanifest", type: "application/json" }, - { format: "yaml", type: "text/yaml" }, - { format: "css", type: "text/css" }, - { format: "xml", type: "text/xml" }, - { format: "jpeg", type: "image/jpeg", noCharset: true }, - { format: "jfif", type: "image/jpeg", noCharset: true }, - { format: "jpg", type: "image/jpeg", noCharset: true }, - { format: "png", type: "image/png", noCharset: true }, - { format: "gif", type: "image/gif", noCharset: true }, - { format: "psd", type: "image/psd", noCharset: true }, - { format: "ico", type: "image/x-icon", noCharset: true }, - { format: "webp", type: "image/webp", noCharset: true }, - { format: "svg", type: "image/svg+xml" }, - { format: "woff2", type: "font/woff2", noCharset: true }, - { format: "woff", type: "font/woff", noCharset: true }, - { format: "ttf", type: "font/ttf", noCharset: true }, - { format: "srt", type: "text/plain" }, - { format: "vtt", type: "text/vtt" }, - { format: "zip", type: "application/zip", noCharset: true }, - { format: "pdf", type: "application/pdf" } -]; - -/** - * @param {string} iString - * @returns {string} - */ -const SafeDecode = (iString) => { - if (typeof iString !== "string") return iString; - - try { - const decoded = decodeURIComponent(iString); - return SafeEscape(decoded); - } catch (e) { - return SafeEscape(iString); - } -}; - -/** - * @param {string} iString - * @returns {string} - */ -const SafeEscape = (iString) => { - if (typeof iString !== "string") return iString; - - return iString - .replace(/(\/+)/gi, "/") - .replace(/\.\.\%2F/gi, "") - .replace(/\.\.\//g, ""); -}; - -/** - * @param {string} reqHeaders - * @returns {{[name: string]: string}} - */ -const ParseCookie = (reqHeaders) => { - if (!reqHeaders.cookie) return {}; - - const returningList = {}, - cookies = reqHeaders.cookie; - - cookies.split(";").forEach((cookie) => { - const parts = cookie.split("="), - cookieName = parts.shift().trim(), - cookieValue = parts.join("="); - - try { - returningList[cookieName] = decodeURIComponent(cookieValue); - } catch (e) { - returningList[cookieName] = cookieValue; - } - }); - - return returningList; -}; - -/** - * @param {string} pathname - * @returns {string[]} - */ -const ParsePath = (pathname) => { - const safePathname = SafeURL(SafeDecode(pathname)).pathname; - - return safePathname.replace().split("/").filter(Boolean); -}; - -/** - * @param {string} query - * @returns {{[queryName: string]: string | true}} - */ -const ParseQuery = (query) => { - if (!query) return {}; - - const returningList = {}; - - query - .toString() - .replace(/^\?/, "") - .split("&") - .forEach((queryPair) => { - try { - if (queryPair.split("=")[1]) - returningList[queryPair.split("=")[0]] = decodeURIComponent(queryPair.split("=")[1]); - else returningList[queryPair.split("=")[0]] = true; - } catch (e) { - returningList[queryPair.split("=")[0]] = queryPair.split("=")[1] || true; - } - }); - - return returningList; -}; - -/** - * @param {{[queryName: string]: string | true}} queries - * @returns {string} - */ -const CombineQueries = (queries) => { - if (typeof queries !== "object") return ""; - if (!Object.keys(queries).length) return ""; - - return ( - "?" + - Object.keys(queries) - .map((key) => (queries[key] === true ? key : `${key}=${encodeURIComponent(queries[key])}`)) - .join("&") - ); -}; - -/** - * @param {string} pathname - * @returns {URL} - */ -const SafeURL = (pathname) => { - if (typeof pathname !== "string") return new URL("/", `https://mirea.xyz`); - - return new URL(pathname.replace(/\/+/g, "/"), `https://mirea.xyz`); -}; - -/** - * @param {string} location - * @returns {string} - */ -const SetMIMEType = (location) => { - const filename = location.toString().split("/").pop(), - index = TYPES_ACCORDANCE.findIndex((type) => type.format === filename.split(".").pop()); - - if (!filename.match(/\./)) return "text/plain"; - else if (index > -1) return TYPES_ACCORDANCE[index].type; - else return filename.split(".").pop(); -}; - -/** - * @param {string} location - * @returns {string} - */ -const SetCompleteMIMEType = (location) => { - const filename = location.toString().split("/").pop(), - index = TYPES_ACCORDANCE.findIndex((type) => type.format === filename.split(".").pop()); - - if (!filename.match(/\./)) return "text/plain"; - else if (index > -1) { - if (TYPES_ACCORDANCE[index].noCharset) return TYPES_ACCORDANCE[index].type; - else return TYPES_ACCORDANCE[index].type + "; charset=UTF-8"; - } else return filename.split(".").pop(); -}; - -/** @type {import("../types").UtilsType} */ -module.exports = { - SafeDecode, - SafeEscape, - SafeURL, - ParseCookie, - ParsePath, - ParseQuery, - CombineQueries, - SetMIMEType, - SetCompleteMIMEType -}; diff --git a/docker-compose.yml b/docker-compose.yml index 562c275..a8263d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,12 @@ name: mss volumes: mss-keycloak-postgres: name: mss-keycloak-postgres + mss-prometheus-data: + name: mss-prometheus-data + mss-grafana-data: + name: mss-grafana-data + mss-grafana-home: + name: mss-grafana-home services: mss-backend: @@ -98,3 +104,28 @@ services: restart: always ports: - 127.0.0.1:${PANEL_PUBLISH_PORT}:80 + + mss-prometheus: + container_name: mss-prometheus + image: prom/prometheus + restart: always + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml + - mss-prometheus-data:/prometheus + + mss-grafana: + container_name: mss-grafana + image: grafana/grafana + restart: on-failure + depends_on: + - mss-prometheus + environment: + GF_AUTH_DISABLE_LOGIN_FORM: true + GF_AUTH_ANONYMOUS_ENABLED: true + GF_AUTH_ANONYMOUS_ORG_ROLE: Admin + volumes: + - ./monitoring/grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml + - ./monitoring/grafana/dashboard.yml:/etc/grafana/provisioning/dashboards/dashboard.yml + - ./monitoring/grafana/node-dashboard.json:/etc/grafana/provisioning/dashboards/node-dashboard.json + - mss-grafana-data:/var/lib/grafana + - mss-grafana-home:/usr/share/grafana diff --git a/docker.env b/docker.env index a92005b..1493339 100644 --- a/docker.env +++ b/docker.env @@ -1,3 +1,13 @@ BACKEND_PUBLISH_PORT=8000 -NOTIFIER_PUBLISH_PORT=8001 FRONTEND_PUBLISH_PATH=/usr/share/nginx/mss/ +NOTIFIER_PUBLISH_PORT=8001 + +KEYCLOAK_PUBLISH_PORT=8002 +KEYCLOAK_HOSTNAME_URL=https://keycloak.domain.tld +KEYCLOAK_ADMIN=insert +KEYCLOAK_ADMIN_PASSWORD=insert +KEYCLOAK_POSTGRES_DB=pgdb +KEYCLOAK_POSTGRES_USER=pguser +KEYCLOAK_POSTGRES_PASSWORD=pgpasswrod + +PANEL_PUBLISH_PORT=8003 diff --git a/monitoring/.gitignore b/monitoring/.gitignore new file mode 100644 index 0000000..7313734 --- /dev/null +++ b/monitoring/.gitignore @@ -0,0 +1 @@ +*.dev* \ No newline at end of file diff --git a/monitoring/grafana/dashboard.yml b/monitoring/grafana/dashboard.yml new file mode 100644 index 0000000..22ef1cf --- /dev/null +++ b/monitoring/grafana/dashboard.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: "Prometheus" + orgId: 1 + folder: "" + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/monitoring/grafana/datasource.yml b/monitoring/grafana/datasource.yml new file mode 100644 index 0000000..2b2f827 --- /dev/null +++ b/monitoring/grafana/datasource.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://mss-prometheus:9090 + basicAuth: false + isDefault: true + editable: true diff --git a/monitoring/grafana/node-dashboard.json b/monitoring/grafana/node-dashboard.json new file mode 100644 index 0000000..0a48840 --- /dev/null +++ b/monitoring/grafana/node-dashboard.json @@ -0,0 +1,359 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 7, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 8, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "7.0.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(http_request_duration_seconds_count[1m]))", + "interval": "", + "legendFormat": "Requests per minute", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rate: RMP", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 8, + "x": 8, + "y": 0 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "7.0.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(http_request_duration_seconds_count{code=~\"4.*\"}[1m]))", + "interval": "", + "legendFormat": "4xx", + "refId": "A" + }, + { + "expr": "sum(increase(http_request_duration_seconds_count{code=~\"5.*\"}[1m]))", + "interval": "", + "legendFormat": "5xx", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Errors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(http_request_duration_seconds_sum{code=~\"2.*\"}[5m]) / rate(http_request_duration_seconds_count{code=~\"2.*\"}[5m]) * 1000", + "interval": "", + "legendFormat": "Average {{route}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Processing time by requests (ms)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "ms", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 25, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + }, + "timezone": "", + "title": "MSS Backend Dashboard", + "uid": "mss", + "version": 2 +} diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml new file mode 100644 index 0000000..5caebdd --- /dev/null +++ b/monitoring/prometheus.yml @@ -0,0 +1,7 @@ +global: + scrape_interval: 5s + +scrape_configs: + - job_name: "backend-server" + static_configs: + - targets: ["172.25.240.1:80"] diff --git a/panel/auth/exchange-code.js b/panel/auth/exchange-code.js index 9917adf..1909ffa 100644 --- a/panel/auth/exchange-code.js +++ b/panel/auth/exchange-code.js @@ -1,13 +1,13 @@ import fetch from "node-fetch"; -import ReadConfig from "../util/read-config.js"; +import readConfig from "../util/read-config.js"; -const PANEL_CONFIG = ReadConfig(); +const PANEL_CONFIG = readConfig(); /** * @param {string} code * @returns {Promise} */ -export default function ExchangeCodeToToken(code) { +export default function exchangeCodeToToken(code) { if (!code) return Promise.reject(new Error("No code was passed")); return fetch(`${PANEL_CONFIG.KEYCLOAK_ORIGIN}/realms/${PANEL_CONFIG.KEYCLOAK_REALM}/protocol/openid-connect/token`, { diff --git a/panel/auth/validate-token.js b/panel/auth/validate-token.js index 60b62a1..3e6e4cb 100644 --- a/panel/auth/validate-token.js +++ b/panel/auth/validate-token.js @@ -1,13 +1,13 @@ import fetch from "node-fetch"; -import ReadConfig from "../util/read-config.js"; +import readConfig from "../util/read-config.js"; -const PANEL_CONFIG = ReadConfig(); +const PANEL_CONFIG = readConfig(); /** * @param {string} token * @returns {Promise} */ -export default function ValidateAccessToken(token) { +export default function validateAccessToken(token) { if (!token) return Promise.reject(new Error("No token was passed")); return fetch( diff --git a/panel/database/dispatcher.js b/panel/database/dispatcher.js index 158b605..7ecc0cd 100644 --- a/panel/database/dispatcher.js +++ b/panel/database/dispatcher.js @@ -1,8 +1,8 @@ import { MongoClient } from "mongodb"; -import ReadConfig from "../util/read-config.js"; -import Logging from "../util/logging.js"; +import readConfig from "../util/read-config.js"; +import logging from "../util/logging.js"; -const MONGO_CONNECTION_URL = ReadConfig().DATABASE_CONNECTION_URI || "mongodb://127.0.0.1:27017/"; +const MONGO_CONNECTION_URL = readConfig().DATABASE_CONNECTION_URI || "mongodb://127.0.0.1:27017/"; /** * @callback MongoDispatcherCallback @@ -31,18 +31,19 @@ export default class MongoDispatcher { */ this.events = {}; - MongoClient.connect(MONGO_CONNECTION_URL, {}) - .then((connectedClient) => { - this.DB = connectedClient.db(dbName); + if (process.env.NODE_ENV !== "test") + MongoClient.connect(MONGO_CONNECTION_URL, {}) + .then((connectedClient) => { + this.DB = connectedClient.db(dbName); - this.on("close", () => { - this.DB = null; - connectedClient.close(); + this.on("close", () => { + this.DB = null; + connectedClient.close(); + }); + }) + .catch((e) => { + logging("Error with connection to MongoDB on start-up", e); }); - }) - .catch((e) => { - Logging("Error with connection to MongoDB on start-up", mongoError); - }); } /** diff --git a/panel/database/methods.js b/panel/database/methods.js index 81078a4..549b08e 100644 --- a/panel/database/methods.js +++ b/panel/database/methods.js @@ -1,7 +1,7 @@ -import ReadConfig from "../util/read-config.js"; +import readConfig from "../util/read-config.js"; import MongoDispatcher from "./dispatcher.js"; -const mongoDispatcher = new MongoDispatcher(ReadConfig().DATABASE_NAME); +const mongoDispatcher = new MongoDispatcher(readConfig().DATABASE_NAME); const DB_METHODS = { listAllParams() { diff --git a/panel/panel.config.json b/panel/panel.config.json index 9c180dd..992ac63 100644 --- a/panel/panel.config.json +++ b/panel/panel.config.json @@ -4,6 +4,8 @@ "KEYCLOAK_CLIENT_ID": "client-id", "KEYCLOAK_CLIENT_SECRET": "client-secret", + "GRAFANA_ORIGIN": "http://mss-grafana:3000", + "PANEL_ORIGIN": "https://panel.domain.tld", "PANEL_COOKIE_TTL_SECONDS": 7200, "PANEL_USER_EMAIL": "email@domain.tld", diff --git a/panel/panel.js b/panel/panel.js index dfac74f..f73e7c3 100644 --- a/panel/panel.js +++ b/panel/panel.js @@ -1,37 +1,39 @@ import { createServer } from "http"; -import ReadConfig from "./util/read-config.js"; -import GetQueryParams from "./util/get-query-params.js"; -import GetCookies from "./util/get-cookies.js"; -import ExchangeCodeToToken from "./auth/exchange-code.js"; -import Logging from "./util/logging.js"; -import ValidateAccessToken from "./auth/validate-token.js"; -import ServeStatic from "./static/serve-static.js"; +import readConfig from "./util/read-config.js"; +import exchangeCodeToToken from "./auth/exchange-code.js"; +import logging from "./util/logging.js"; +import validateAccessToken from "./auth/validate-token.js"; +import serveStatic from "./static/serve-static.js"; import DB_METHODS from "./database/methods.js"; -import ReadPayload from "./util/read-payload.js"; +import readPayload from "./util/read-payload.js"; +import { parseCookies, parsePath, parseQuery } from "./util/urls-and-cookies.js"; +import fetch from "node-fetch"; +import { equal } from "assert"; -const PANEL_CONFIG = ReadConfig(); +const PANEL_CONFIG = readConfig(); const IS_PANEL_ORIGIN_SECURE = new URL(PANEL_CONFIG.PANEL_ORIGIN).protocol === "https:"; createServer((req, res) => { const method = req.method; - const path = req.url; - const queries = GetQueryParams(req.url); - const cookies = GetCookies(req.headers); + const requestedURL = req.url; + const path = parsePath(req.url); + const queries = parseQuery(req.url); + const cookies = parseCookies(req.headers); - const SendError = () => { + const sendError = () => { res.statusCode = 500; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("500 Internal Server Error"); }; - const SendForbidden = () => { + const sendForbidden = () => { res.statusCode = 403; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.end("403 Forbidden"); }; - const RedirectToLoginPage = () => { + const redirectToLoginPage = () => { const loginPage = `${PANEL_CONFIG.KEYCLOAK_ORIGIN}/realms/${PANEL_CONFIG.KEYCLOAK_REALM}/protocol/openid-connect/auth?client_id=${PANEL_CONFIG.KEYCLOAK_CLIENT_ID}&scope=openid%20profile&redirect_uri=${PANEL_CONFIG.PANEL_ORIGIN}&response_type=code`; res.statusCode = 302; @@ -40,65 +42,82 @@ createServer((req, res) => { res.end(`Go to ${loginPage}`); }; - ValidateAccessToken(cookies.access_token) + validateAccessToken(cookies.access_token) .then(() => { - if (path === "/list") { - if (method !== "POST") { - res.statusCode = 405; - res.end("405 Method Not Allowed"); - return; + if (path[0] === "params-panel" || path[0] === "favicon.ico") { + if (path[1] === "api" && path[2] === "list") { + if (method !== "GET") { + res.statusCode = 405; + res.end("405 Method Not Allowed"); + return; + } + + return DB_METHODS.listAllParams() + .then((params) => { + res.statusCode = 200; + res.setHeader("Content-Type", "application/json; charset=utf-8"); + res.end(JSON.stringify(params)); + }) + .catch((e) => { + logging(e); + sendError(); + }); } - return DB_METHODS.listAllParams() - .then((params) => { - res.statusCode = 200; - res.setHeader("Content-Type", "application/json; charset=utf-8"); - res.end(JSON.stringify(params)); - }) - .catch((e) => { - Logging(e); - SendError(); - }); - } + if (path[1] === "api" && path[2] === "set") { + if (method !== "POST") { + res.statusCode = 405; + res.end("405 Method Not Allowed"); + return; + } + + return readPayload(req) + .then((readBuffer) => { + try { + return Promise.resolve(JSON.parse(readBuffer.toString())); + } catch (e) { + return Promise.reject(new Error("Malformed JSON")); + } + }) + .then((payload) => DB_METHODS.setParam(payload)) + .then((updatedNumber) => { + res.statusCode = 200; + res.setHeader("Content-Type", "application/json; charset=utf-8"); + res.end(JSON.stringify({ updatedNumber })); + }) + .catch((e) => { + logging(e); + sendError(); + }); + } - if (path === "/set") { - if (method !== "POST") { + if (method !== "GET") { res.statusCode = 405; res.end("405 Method Not Allowed"); return; } - return ReadPayload(req) - .then((readBuffer) => { - try { - return Promise.resolve(JSON.parse(readBuffer.toString())); - } catch (e) { - return Promise.reject(new Error("Malformed JSON")); - } - }) - .then((payload) => DB_METHODS.setParam(payload)) - .then((updatedNumber) => { - res.statusCode = 200; - res.setHeader("Content-Type", "application/json; charset=utf-8"); - res.end(JSON.stringify({ updatedNumber })); - }) - .catch((e) => { - Logging(e); - SendError(); - }); - } - - if (method !== "GET") { - res.statusCode = 405; - res.end("405 Method Not Allowed"); + serveStatic(req, res); return; } - ServeStatic(req, res); + fetch(new URL(requestedURL, PANEL_CONFIG.GRAFANA_ORIGIN).href, { + method, + headers: req.headers, + body: method === "GET" ? undefined : req + }) + .then((grafanaResponse) => { + res.statusCode = grafanaResponse.status; + res.statusMessage = grafanaResponse.statusText; + for (const [name, value] of grafanaResponse.headers) res.setHeader(name, value); + + grafanaResponse.body.pipe(res); + }) + .catch(() => sendError()); }) .catch(() => { if ("session_state" in queries && queries.code) { - ExchangeCodeToToken(queries.code) + exchangeCodeToToken(queries.code) .then((token) => { res.setHeader( "Set-Cookie", @@ -107,16 +126,16 @@ createServer((req, res) => { ).toUTCString()}; ${IS_PANEL_ORIGIN_SECURE ? "Secure; " : ""}HttpOnly; SameSite=Strict` ); - ServeStatic(req, res); + serveStatic(req, res); }) .catch((e) => { console.warn(e); - SendForbidden(); + sendForbidden(); }); return; } - RedirectToLoginPage(); + redirectToLoginPage(); }); }).listen(80); diff --git a/panel/static/favicon.ico b/panel/static/favicon.ico new file mode 100644 index 0000000..0d168f3 Binary files /dev/null and b/panel/static/favicon.ico differ diff --git a/panel/static/index.html b/panel/static/index.html index 15ff0d7..0bf0022 100644 --- a/panel/static/index.html +++ b/panel/static/index.html @@ -44,7 +44,7 @@