From 28c8429edd561bbda1b42cc01d1c7230df992734 Mon Sep 17 00:00:00 2001 From: baldo Date: Mon, 18 Jul 2022 17:49:42 +0200 Subject: [PATCH] Sqlite upgrade and type refactorings --- REFACTOR.md | 1 - package.json | 3 +- server/app.ts | 16 +- server/config.ts | 18 +- server/db/__mocks__/database.ts | 103 ++-- server/db/database.ts | 227 +++++---- server/resources/mailResource.ts | 4 +- server/resources/monitoringResource.ts | 6 +- server/resources/nodeResource.ts | 11 +- server/services/mailService.ts | 76 ++- server/services/mailTemplateService.ts | 8 +- server/services/monitoringService.test.ts | 21 +- server/services/monitoringService.ts | 87 ++-- server/services/nodeService.ts | 57 ++- server/types/config.ts | 35 +- server/types/index.ts | 21 +- server/types/shared.ts | 231 +++++---- server/utils/strings.ts | 8 +- server/utils/urlBuilder.ts | 20 +- yarn.lock | 583 +++++++++++++--------- 20 files changed, 873 insertions(+), 663 deletions(-) diff --git a/REFACTOR.md b/REFACTOR.md index 8747770..ca5baae 100644 --- a/REFACTOR.md +++ b/REFACTOR.md @@ -12,7 +12,6 @@ * Split into seperate packages for server and frontend. * Make admin panel part of new frontend package. * Get rid of grunt. -* Bluebird for promises? * Use generated type guards. ## Mid term diff --git a/package.json b/package.json index 8684c35..f6b2546 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "request": "^2.88.2", "serve-static": "^1.14.1", "sparkson": "^1.3.6", - "sqlite": "^3.0.6" + "sqlite": "^4.1.1", + "sqlite3": "^5.0.9" }, "devDependencies": { "@types/async": "^3.2.15", diff --git a/server/app.ts b/server/app.ts index 115bb78..fa0dba3 100644 --- a/server/app.ts +++ b/server/app.ts @@ -9,7 +9,7 @@ import {promises as fs} from "graceful-fs"; import {config} from "./config"; import type {CleartextPassword, PasswordHash, Username} from "./types"; -import {isString, lift2, to} from "./types"; +import {isString} from "./types"; import Logger from "./logger"; export const app: Express = express(); @@ -17,14 +17,14 @@ export const app: Express = express(); /** * Used to have some password comparison in case the user does not exist to avoid timing attacks. */ -const INVALID_PASSWORD_HASH: PasswordHash = to("$2b$05$JebmV1q/ySuxa89GoJYlc.6SEnj1OZYBOfTf.TYAehcC5HLeJiWPi"); +const INVALID_PASSWORD_HASH: PasswordHash = "$2b$05$JebmV1q/ySuxa89GoJYlc.6SEnj1OZYBOfTf.TYAehcC5HLeJiWPi" as PasswordHash; /** * Trying to implement a timing safe string compare. * * TODO: Write tests for timing. */ -function timingSafeEqual(a: string, b: string): boolean { +function timingSafeEqual(a: T, b: T): boolean { const lenA = a.length; const lenB = b.length; @@ -32,7 +32,7 @@ function timingSafeEqual(a: string, b: string): boolean { let different = Math.abs(lenA - lenB); // Make sure b is always the same length as a. Use slice to try avoiding optimizations. - b = different === 0 ? b.slice() : a.slice(); + b = (different === 0 ? b.slice() : a.slice()) as T; for (let i = 0; i < lenA; i += 1) { different += Math.abs(a.charCodeAt(i) - b.charCodeAt(i)); @@ -50,15 +50,15 @@ async function isValidLogin(username: Username, password: CleartextPassword): Pr // Iterate over all users every time to reduce risk of timing attacks. for (const userConfig of config.server.internal.users) { - if (lift2(timingSafeEqual)(username, userConfig.username)) { + if (timingSafeEqual(username, userConfig.username)) { passwordHash = userConfig.passwordHash; } } // Always compare some password even if the user does not exist to reduce risk of timing attacks. const isValidPassword = await bcrypt.compare( - password.value, - passwordHash?.value || INVALID_PASSWORD_HASH.value + password, + passwordHash || INVALID_PASSWORD_HASH ); // Make sure password is only considered valid is user exists and therefor passwordHash is not undefined. @@ -74,7 +74,7 @@ export function init(): void { realm: 'Knotenformular - Intern' }, function (username: string, password: string, callback: BasicAuthCheckerCallback): void { - isValidLogin(to(username), to(password)) + isValidLogin(username as Username, password as CleartextPassword) .then(result => callback(result)) .catch(err => { Logger.tag('login').error(err); diff --git a/server/config.ts b/server/config.ts index 0c0dabd..a82ef61 100644 --- a/server/config.ts +++ b/server/config.ts @@ -3,17 +3,17 @@ import commandLineUsage from "command-line-usage" import fs from "graceful-fs" import url from "url" import {parse} from "sparkson" -import {Config, Version} from "./types" +import {Config, Url, Version} from "./types" // @ts-ignore export let config: Config = {}; -export let version: Version = "unknown"; +export let version: Version = "unknown" as Version; export function parseCommandLine(): void { const commandLineDefs = [ - { name: 'help', alias: 'h', type: Boolean, description: 'Show this help' }, - { name: 'config', alias: 'c', type: String, description: 'Location of config.json' }, - { name: 'version', alias: 'v', type: Boolean, description: 'Show ffffng version' } + {name: 'help', alias: 'h', type: Boolean, description: 'Show this help'}, + {name: 'config', alias: 'c', type: String, description: 'Location of config.json'}, + {name: 'version', alias: 'v', type: Boolean, description: 'Show ffffng version'} ]; let commandLineOptions; @@ -34,7 +34,7 @@ export function parseCommandLine(): void { version = JSON.parse(fs.readFileSync(packageJsonFile, 'utf8')).version; } - function usage () { + function usage() { console.log(commandLineUsage([ { header: 'ffffng - ' + version + ' - Freifunk node management form', @@ -70,8 +70,10 @@ export function parseCommandLine(): void { config = parse(Config, configJSON); - function stripTrailingSlash(url: string): string { - return url.endsWith("/") ? url.substr(0, url.length - 1) : url; + function stripTrailingSlash(url: Url): Url { + return url.endsWith("/") + ? url.substr(0, url.length - 1) as Url + : url; } config.server.baseUrl = stripTrailingSlash(config.server.baseUrl); diff --git a/server/db/__mocks__/database.ts b/server/db/__mocks__/database.ts index 420f349..4345393 100644 --- a/server/db/__mocks__/database.ts +++ b/server/db/__mocks__/database.ts @@ -1,93 +1,48 @@ -import {Database, Statement} from "sqlite"; +import {RunResult, SqlType, Statement, TypedDatabase} from "../database"; +import * as sqlite3 from "sqlite3"; -export async function init(): Promise {} +export async function init(): Promise { +} -export class MockStatement implements Statement { - constructor() {} - - readonly changes: number = 0; - readonly lastID: number = 0; - readonly sql: string = ""; - - async all(): Promise; - async all(...params: any[]): Promise; - async all(): Promise; - async all(...params: any[]): Promise; - all(...params: any[]): any { +export class MockDatabase implements TypedDatabase { + constructor() { } - async bind(): Promise; - async bind(...params: any[]): Promise; - async bind(...params: any[]): Promise { - return mockStatement(); + async on(event: string, listener: any): Promise { } - async each(callback?: (err: Error, row: any) => void): Promise; - async each(...params: any[]): Promise; - async each(...callback: (((err: Error, row: any) => void) | any)[]): Promise { + async run(sql: SqlType, ...params: any[]): Promise { + return { + stmt: new Statement(new sqlite3.Statement()), + }; + } + + async get(sql: SqlType, ...params: any[]): Promise { + return undefined; + } + + async each(sql: SqlType, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, param2: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, param2: any, param3: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, ...params: any[]): Promise; + async each(sql: SqlType, ...callback: (any)[]): Promise { return 0; } - async finalize(): Promise {} - - get(): Promise; - get(...params: any[]): Promise; - get(): Promise; - get(...params: any[]): Promise; - get(...params: any[]): any { - } - - async reset(): Promise { - return mockStatement(); - } - - async run(): Promise; - async run(...params: any[]): Promise; - async run(...params: any[]): Promise { - return mockStatement(); - } -} - -function mockStatement(): Statement { - return new MockStatement(); -} - -export class MockDatabase implements Database { - constructor() {} - - async close(): Promise {} - - async run(...args: any): Promise { - return mockStatement(); - } - - async get(...args: any): Promise {} - - async all(...args: any): Promise { + async all(sql: SqlType, ...params: any[]): Promise { return []; } - async exec(...args: any): Promise { - return this; + async exec(sql: SqlType, ...params: any[]): Promise { } - async each(...args: any): Promise { - return 0; + + async prepare(sql: SqlType, ...params: any[]): Promise { + return new Statement(new sqlite3.Statement()); } - - async prepare(...args: any): Promise { - return mockStatement(); - } - - configure(...args: any): void {} - - async migrate(...args: any): Promise { - return this; - } - - on(...args: any): void {} } export const db: MockDatabase = new MockDatabase(); -export {Database, Statement} +export {TypedDatabase, Statement} diff --git a/server/db/database.ts b/server/db/database.ts index 3ced994..9e02eca 100644 --- a/server/db/database.ts +++ b/server/db/database.ts @@ -4,12 +4,125 @@ import glob from "glob"; import path from "path"; import {config} from "../config"; import Logger from "../logger"; -import sqlite, {Database, Statement} from "sqlite"; +import {Database, ISqlite, open, Statement} from "sqlite"; +import * as sqlite3 from "sqlite3"; const pglob = util.promisify(glob); const pReadFile = util.promisify(fs.readFile); -async function applyPatch(db: sqlite.Database, file: string): Promise { +export type RunResult = ISqlite.RunResult; +export type SqlType = ISqlite.SqlType; + +export interface TypedDatabase { + /** + * @see Database.on + */ + on(event: string, listener: any): Promise; + + /** + * @see Database.run + */ + run(sql: SqlType, ...params: any[]): Promise; + + /** + * @see Database.get + */ + get(sql: SqlType, ...params: any[]): Promise; + + /** + * @see Database.each + */ + each(sql: SqlType, callback: (err: any, row: T) => void): Promise; + + each(sql: SqlType, param1: any, callback: (err: any, row: T) => void): Promise; + + each(sql: SqlType, param1: any, param2: any, callback: (err: any, row: T) => void): Promise; + + each(sql: SqlType, param1: any, param2: any, param3: any, callback: (err: any, row: T) => void): Promise; + + each(sql: SqlType, ...params: any[]): Promise; + + /** + * @see Database.all + */ + all(sql: SqlType, ...params: any[]): Promise; + + /** + * @see Database.exec + */ + exec(sql: SqlType, ...params: any[]): Promise; + + /** + * @see Database.prepare + */ + prepare(sql: SqlType, ...params: any[]): Promise; +} + +/** + * Typesafe database wrapper. + * + * @see Database + */ +class DatabasePromiseWrapper implements TypedDatabase { + private db: Promise; + + constructor() { + this.db = new Promise((resolve, reject) => { + open({ + filename: config.server.databaseFile, + driver: sqlite3.Database, + }) + .then(resolve) + .catch(reject); + }); + this.db.catch(err => { + Logger.tag('database', 'init').error('Error initializing database: ', err); + process.exit(1); + }); + } + + async on(event: string, listener: any): Promise { + const db = await this.db; + db.on(event, listener); + } + + async run(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + return db.run(sql, ...params); + } + + async get(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + return await db.get(sql, ...params); + } + + async each(sql: SqlType, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, param2: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, param1: any, param2: any, param3: any, callback: (err: any, row: T) => void): Promise; + async each(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + // @ts-ignore + return await db.each.apply(db, arguments); + } + + async all(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + return (await db.all(sql, ...params)); + } + + async exec(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + return await db.exec(sql, ...params); + } + + async prepare(sql: SqlType, ...params: any[]): Promise { + const db = await this.db; + return await db.prepare(sql, ...params); + } +} + +async function applyPatch(db: TypedDatabase, file: string): Promise { Logger.tag('database', 'migration').info('Checking if patch need to be applied: %s', file); const contents = await pReadFile(file); @@ -23,22 +136,22 @@ async function applyPatch(db: sqlite.Database, file: string): Promise { } const sql = 'BEGIN TRANSACTION;\n' + - contents.toString() + '\n' + - 'INSERT INTO schema_version (version) VALUES (\'' + version + '\');\n' + - 'END TRANSACTION;'; + contents.toString() + '\n' + + 'INSERT INTO schema_version (version) VALUES (\'' + version + '\');\n' + + 'END TRANSACTION;'; await db.exec(sql); Logger.tag('database', 'migration').info('Patch successfully applied: %s', file); } -async function applyMigrations(db: sqlite.Database): Promise { +async function applyMigrations(db: TypedDatabase): Promise { Logger.tag('database', 'migration').info('Migrating database...'); const sql = 'BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS schema_version (\n' + - ' version VARCHAR(255) PRIMARY KEY ASC,\n' + - ' applied_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL\n' + - '); END TRANSACTION;'; + ' version VARCHAR(255) PRIMARY KEY ASC,\n' + + ' applied_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL\n' + + '); END TRANSACTION;'; await db.exec(sql); @@ -48,106 +161,18 @@ async function applyMigrations(db: sqlite.Database): Promise { } } -const dbPromise = new Promise((resolve, reject) => { - sqlite.open(config.server.databaseFile) - .then(resolve) - .catch(reject); -}); +export const db: TypedDatabase = new DatabasePromiseWrapper(); export async function init(): Promise { Logger.tag('database').info('Setting up database: %s', config.server.databaseFile); - - let db: Database; - try { - db = await dbPromise; - } - catch (error) { - Logger.tag('database').error('Error initialzing database:', error); - throw error; - } - - db.on('profile', (sql, time) => Logger.tag('database').profile('[%sms]\t%s', time, sql)); + await db.on('profile', (sql: string, time: number) => Logger.tag('database').profile('[%sms]\t%s', time, sql)); try { await applyMigrations(db); - } - catch (error) { + } catch (error) { Logger.tag('database').error('Error migrating database:', error); throw error; } } -/** - * Wrapper around a Promise providing the same interface as the Database itself. - */ -class DatabasePromiseWrapper implements Database { - constructor(private db: Promise) { - db.catch(err => { - Logger.tag('database', 'init').error('Error initializing database: ', err); - process.exit(1); - }); - } - - async close() { - const db = await this.db; - // @ts-ignore - return await db.close.apply(db, arguments); - } - - async run() { - const db = await this.db; - // @ts-ignore - return await db.run.apply(db, arguments); - } - - async get() { - const db = await this.db; - // @ts-ignore - return await db.get.apply(db, arguments); - } - - async all() { - const db = await this.db; - // @ts-ignore - return await db.all.apply(db, arguments); - } - - async exec() { - const db = await this.db; - // @ts-ignore - return await db.exec.apply(db, arguments); - } - - async each() { - const db = await this.db; - // @ts-ignore - return await db.each.apply(db, arguments); - } - - async prepare() { - const db = await this.db; - // @ts-ignore - return await db.prepare.apply(db, arguments); - } - - async configure() { - const db = await this.db; - // @ts-ignore - return await db.configure.apply(db, arguments); - } - - async migrate() { - const db = await this.db; - // @ts-ignore - return await db.migrate.apply(db, arguments); - } - - async on() { - const db = await this.db; - // @ts-ignore - return await db.on.apply(db, arguments); - } -} - -export const db: Database = new DatabasePromiseWrapper(dbPromise); -export {Database, Statement}; +export {Statement}; diff --git a/server/resources/mailResource.ts b/server/resources/mailResource.ts index ec5a68a..e3c6dc7 100644 --- a/server/resources/mailResource.ts +++ b/server/resources/mailResource.ts @@ -2,7 +2,7 @@ import CONSTRAINTS from "../validation/constraints"; import ErrorTypes from "../utils/errorTypes"; import * as MailService from "../services/mailService"; import * as Resources from "../utils/resources"; -import {normalizeString} from "../utils/strings"; +import {normalizeString, parseInteger} from "../utils/strings"; import {forConstraint} from "../validation/validator"; import {Request, Response} from "express"; import {Mail, MailId} from "../types"; @@ -16,7 +16,7 @@ async function withValidMailId(req: Request): Promise { throw {data: 'Invalid mail id.', type: ErrorTypes.badRequest}; } - return id; + return parseInteger(id) as MailId; } async function doGet(req: Request): Promise { diff --git a/server/resources/monitoringResource.ts b/server/resources/monitoringResource.ts index 021c046..3451641 100644 --- a/server/resources/monitoringResource.ts +++ b/server/resources/monitoringResource.ts @@ -7,7 +7,7 @@ import * as Resources from "../utils/resources"; import {normalizeString} from "../utils/strings"; import {forConstraint} from "../validation/validator"; import {Request, Response} from "express"; -import {MonitoringToken, to} from "../types"; +import {MonitoringToken} from "../types"; const isValidToken = forConstraint(CONSTRAINTS.token, false); @@ -39,7 +39,7 @@ export function confirm(req: Request, res: Response): void { if (!isValidToken(token)) { return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest}); } - const validatedToken: MonitoringToken = to(token); + const validatedToken: MonitoringToken = token as MonitoringToken; MonitoringService.confirm(validatedToken) .then(node => Resources.success(res, { @@ -59,7 +59,7 @@ export function disable(req: Request, res: Response): void { if (!isValidToken(token)) { return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest}); } - const validatedToken: MonitoringToken = to(token); + const validatedToken: MonitoringToken = token as MonitoringToken; MonitoringService.disable(validatedToken) .then(node => Resources.success(res, { diff --git a/server/resources/nodeResource.ts b/server/resources/nodeResource.ts index dacd9cf..1331c2a 100644 --- a/server/resources/nodeResource.ts +++ b/server/resources/nodeResource.ts @@ -8,9 +8,8 @@ import * as NodeService from "../services/nodeService"; import {normalizeMac, normalizeString} from "../utils/strings"; import {forConstraint, forConstraints} from "../validation/validator"; import * as Resources from "../utils/resources"; -import {Entity} from "../utils/resources"; import {Request, Response} from "express"; -import {EnhancedNode, isNodeSortField, MAC, Node, to, Token} from "../types"; +import {EnhancedNode, isNodeSortField, MAC, Node, Token} from "../types"; const nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring']; @@ -49,7 +48,7 @@ export function update (req: Request, res: Response): void { if (!isValidToken(token)) { return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest}); } - const validatedToken: Token = to(token); + const validatedToken: Token = token as Token; const node = getNormalizedNodeData(data); if (!isValidNode(node)) { @@ -68,7 +67,7 @@ export function remove(req: Request, res: Response): void { if (!isValidToken(token)) { return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest}); } - const validatedToken: Token = to(token); + const validatedToken: Token = token as Token; NodeService.deleteNode(validatedToken) .then(() => Resources.success(res, {})) @@ -80,7 +79,7 @@ export function get(req: Request, res: Response): void { if (!isValidToken(token)) { return Resources.error(res, {data: 'Invalid token.', type: ErrorTypes.badRequest}); } - const validatedToken: Token = to(token); + const validatedToken: Token = token as Token; NodeService.getNodeDataByToken(validatedToken) .then(node => Resources.success(res, node)) @@ -101,7 +100,7 @@ async function doGetAll(req: Request): Promise<{ total: number; pageNodes: any } const nodeStateByMac = await MonitoringService.getByMacs(macs); const enhancedNodes: EnhancedNode[] = _.map(realNodes, (node: Node): EnhancedNode => { - const nodeState = nodeStateByMac[node.mac.value]; + const nodeState = nodeStateByMac[node.mac]; if (nodeState) { return deepExtend({}, node, { site: nodeState.site, diff --git a/server/services/mailService.ts b/server/services/mailService.ts index 0b3fc24..1068375 100644 --- a/server/services/mailService.ts +++ b/server/services/mailService.ts @@ -9,7 +9,29 @@ import Logger from "../logger"; import * as MailTemplateService from "./mailTemplateService"; import * as Resources from "../utils/resources"; import {RestParams} from "../utils/resources"; -import {isMailSortField, Mail, MailData, MailId, MailSortField, MailType} from "../types"; +import { + EmailAddress, isJSONObject, + isMailSortField, isMailType, JSONObject, + Mail, + MailData, + MailId, + MailSortField, + MailType, + parseJSON, + UnixTimestampSeconds +} from "../types"; +import ErrorTypes from "../utils/errorTypes"; + +type EmaiQueueRow = { + id: MailId, + created_at: UnixTimestampSeconds, + data: string, + email: string, + failures: number, + modified_at: UnixTimestampSeconds, + recipient: EmailAddress, + sender: EmailAddress, +}; const MAIL_QUEUE_DB_BATCH_SIZE = 50; @@ -24,7 +46,7 @@ function transporter() { { transport: 'smtp', pool: true - } + } as JSONObject )); MailTemplateService.configureTransporter(transporterSingleton); @@ -57,18 +79,29 @@ async function sendMail(options: Mail): Promise { } async function findPendingMailsBefore(beforeMoment: Moment, limit: number): Promise { - const rows = await db.all( + const rows = await db.all( 'SELECT * FROM email_queue WHERE modified_at < ? AND failures < ? ORDER BY id ASC LIMIT ?', [beforeMoment.unix(), 5, limit], ); - return _.map(rows, row => deepExtend( - {}, - row, - { - data: JSON.parse(row.data) + return rows.map(row => { + const mailType = row.email; + if (!isMailType(mailType)) { + throw new Error(`Invalid mailtype in database: ${mailType}`); } - )); + const data = parseJSON(row.data); + if (!isJSONObject(data)) { + throw new Error(`Invalid email data in database: ${typeof data}`); + } + return { + id: row.id, + email: mailType, + sender: row.sender, + recipient: row.recipient, + data, + failures: row.failures, + }; + }); } async function removePendingMailFromQueue(id: MailId): Promise { @@ -85,8 +118,7 @@ async function incrementFailureCounterForPendingEmail(id: MailId): Promise async function sendPendingMail(pendingMail: Mail): Promise { try { await sendMail(pendingMail); - } - catch (error) { + } catch (error) { // we only log the error and increment the failure counter as we want to continue with pending mails Logger.tag('mail', 'queue').error('Error sending pending mail[' + pendingMail.id + ']:', error); @@ -98,10 +130,14 @@ async function sendPendingMail(pendingMail: Mail): Promise { } async function doGetMail(id: MailId): Promise { - return await db.get('SELECT * FROM email_queue WHERE id = ?', [id]); + const row = await db.get('SELECT * FROM email_queue WHERE id = ?', [id]); + if (row === undefined) { + throw {data: 'Mail not found.', type: ErrorTypes.notFound}; + } + return row; } -export async function enqueue (sender: string, recipient: string, email: MailType, data: MailData): Promise { +export async function enqueue(sender: string, recipient: string, email: MailType, data: MailData): Promise { if (!_.isPlainObject(data)) { throw new Error('Unexpected data: ' + data); } @@ -113,17 +149,17 @@ export async function enqueue (sender: string, recipient: string, email: MailTyp ); } -export async function getMail (id: MailId): Promise { +export async function getMail(id: MailId): Promise { return await doGetMail(id); } -export async function getPendingMails (restParams: RestParams): Promise<{mails: Mail[], total: number}> { - const row = await db.get( +export async function getPendingMails(restParams: RestParams): Promise<{ mails: Mail[], total: number }> { + const row = await db.get<{ total: number }>( 'SELECT count(*) AS total FROM email_queue', [], ); - const total = row.total; + const total = row?.total || 0; const filter = Resources.filterClause( restParams, @@ -143,11 +179,11 @@ export async function getPendingMails (restParams: RestParams): Promise<{mails: } } -export async function deleteMail (id: MailId): Promise { +export async function deleteMail(id: MailId): Promise { await removePendingMailFromQueue(id); } -export async function resetFailures (id: MailId): Promise { +export async function resetFailures(id: MailId): Promise { const statement = await db.run( 'UPDATE email_queue SET failures = 0, modified_at = ? WHERE id = ?', [moment().unix(), id], @@ -160,7 +196,7 @@ export async function resetFailures (id: MailId): Promise { return await doGetMail(id); } -export async function sendPendingMails (): Promise { +export async function sendPendingMails(): Promise { Logger.tag('mail', 'queue').debug('Start sending pending mails...'); const startTime = moment(); diff --git a/server/services/mailTemplateService.ts b/server/services/mailTemplateService.ts index 8e6f1c8..e9c617f 100644 --- a/server/services/mailTemplateService.ts +++ b/server/services/mailTemplateService.ts @@ -13,7 +13,13 @@ import {MailData, Mail} from "../types"; const templateBasePath = __dirname + '/../mailTemplates'; const snippetsBasePath = templateBasePath + '/snippets'; -const templateFunctions: {[key: string]: (...data: MailData) => string} = {}; +const templateFunctions: { + [key: string]: + | ((name: string, data: MailData) => string) + | ((data: MailData) => string) + | ((href: string, text: string) => string) + | ((unix: number) => string) +} = {}; function renderSnippet(this: any, name: string, data: MailData): string { const snippetFile = snippetsBasePath + '/' + name + '.html'; diff --git a/server/services/monitoringService.test.ts b/server/services/monitoringService.test.ts index 0b315c6..bb6259a 100644 --- a/server/services/monitoringService.test.ts +++ b/server/services/monitoringService.test.ts @@ -1,6 +1,6 @@ import moment from 'moment'; import {ParsedNode, parseNode, parseNodesJson, parseTimestamp} from "./monitoringService"; -import {MAC, OnlineState, to} from "../types"; +import {Domain, MAC, OnlineState, Site} from "../types"; import Logger from '../logger'; import {MockLogger} from "../__mocks__/logger"; @@ -44,6 +44,7 @@ test('parseTimestamp() should fail parsing empty timestamp string', () => { test('parseTimestamp() should fail parsing invalid timestamp string', () => { // given + // noinspection UnnecessaryLocalVariableJS const timestamp = TIMESTAMP_INVALID_STRING; // when @@ -240,12 +241,12 @@ test('parseNode() should succeed parsing node without site and domain', () => { // then const expectedParsedNode: ParsedNode = { - mac: to("12:34:56:78:90:AB"), + mac: "12:34:56:78:90:AB" as MAC, importTimestamp: importTimestamp, state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), - site: to(""), - domain: to(""), + site: "" as Site, + domain: "" as Domain, }; expect(parseNode(importTimestamp, nodeData)).toEqual(expectedParsedNode); }); @@ -272,12 +273,12 @@ test('parseNode() should succeed parsing node with site and domain', () => { // then const expectedParsedNode: ParsedNode = { - mac: to("12:34:56:78:90:AB"), + mac: "12:34:56:78:90:AB" as MAC, importTimestamp: importTimestamp, state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), - site: to("test-site"), - domain: to("test-domain") + site: "test-site" as Site, + domain: "test-domain" as Domain, }; expect(parseNode(importTimestamp, nodeData)).toEqual(expectedParsedNode); }); @@ -461,12 +462,12 @@ test('parseNodesJson() should parse valid nodes', () => { // then const expectedParsedNode: ParsedNode = { - mac: to("12:34:56:78:90:AB"), + mac: "12:34:56:78:90:AB" as MAC, importTimestamp: parseTimestamp(TIMESTAMP_VALID_STRING), state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), - site: to("test-site"), - domain: to("test-domain"), + site: "test-site" as Site, + domain: "test-domain" as Domain, }; expect(result.importTimestamp.isValid()).toBe(true); diff --git a/server/services/monitoringService.ts b/server/services/monitoringService.ts index 875bbc5..962f988 100644 --- a/server/services/monitoringService.ts +++ b/server/services/monitoringService.ts @@ -3,7 +3,7 @@ import moment, {Moment, unitOfTime} from "moment"; import request from "request"; import {config} from "../config"; -import {db, Statement} from "../db/database"; +import {db, RunResult} from "../db/database"; import * as DatabaseUtil from "../utils/databaseUtil"; import ErrorTypes from "../utils/errorTypes"; import Logger from "../logger"; @@ -12,14 +12,15 @@ import * as MailService from "../services/mailService"; import * as NodeService from "../services/nodeService"; import * as Resources from "../utils/resources"; import {RestParams} from "../utils/resources"; -import {normalizeMac} from "../utils/strings"; +import {normalizeMac, parseInteger} from "../utils/strings"; import {monitoringDisableUrl} from "../utils/urlBuilder"; import CONSTRAINTS from "../validation/constraints"; import {forConstraint} from "../validation/validator"; import { Domain, - equal, + Hostname, isMonitoringSortField, + isOnlineState, MAC, MailType, MonitoringSortField, @@ -29,10 +30,25 @@ import { NodeStateData, OnlineState, Site, - to, UnixTimestampSeconds } from "../types"; +type NodeStateRow = { + id: number, + created_at: UnixTimestampSeconds, + domain: Domain | null, + hostname: Hostname | null, + import_timestamp: UnixTimestampSeconds, + last_seen: UnixTimestampSeconds, + last_status_mail_sent: string | null, + last_status_mail_type: string | null, + mac: MAC, + modified_at: UnixTimestampSeconds, + monitoring_state: string | null, + site: Site | null, + state: string, +}; + const MONITORING_STATE_MACS_CHUNK_SIZE = 100; const NEVER_ONLINE_NODES_DELETION_CHUNK_SIZE = 20; const MONITORING_MAILS_DB_BATCH_SIZE = 50; @@ -193,7 +209,7 @@ export function parseNode(importTimestamp: Moment, nodeData: any): ParsedNode { 'Node ' + nodeId + ': Invalid MAC: ' + nodeData.nodeinfo.network.mac ); } - const mac = normalizeMac(nodeData.nodeinfo.network.mac); + const mac = normalizeMac(nodeData.nodeinfo.network.mac) as MAC; if (!_.isPlainObject(nodeData.flags)) { throw new Error( @@ -214,23 +230,23 @@ export function parseNode(importTimestamp: Moment, nodeData: any): ParsedNode { ); } - let site = null; + let site = "" as Site; // FIXME: Handle this if (_.isPlainObject(nodeData.nodeinfo.system) && _.isString(nodeData.nodeinfo.system.site_code)) { - site = nodeData.nodeinfo.system.site_code; + site = nodeData.nodeinfo.system.site_code as Site; } - let domain = null; + let domain = "" as Domain; // FIXME: Handle this if (_.isPlainObject(nodeData.nodeinfo.system) && _.isString(nodeData.nodeinfo.system.domain_code)) { - domain = nodeData.nodeinfo.system.domain_code; + domain = nodeData.nodeinfo.system.domain_code as Domain; } return { - mac: to(mac), + mac, importTimestamp: importTimestamp, state: isOnline ? OnlineState.ONLINE : OnlineState.OFFLINE, lastSeen: lastSeen, - site: to(site || ''), // FIXME: Handle this - domain: to(domain || '') // FIXME: Handle this + site, + domain, }; } @@ -279,7 +295,7 @@ export function parseNodesJson(body: string): NodesParsingResult { return result; } -async function updateSkippedNode(id: NodeId, node?: Node): Promise { +async function updateSkippedNode(id: NodeId, node?: Node): Promise { return await db.run( 'UPDATE node_state ' + 'SET hostname = ?, monitoring_state = ?, modified_at = ?' + @@ -352,8 +368,7 @@ async function sendMonitoringMailsBatched( { node: node, lastSeen: nodeState.last_seen, - disableUrl: monitoringDisableUrl(monitoringToken) - + disableUrl: monitoringDisableUrl(monitoringToken), } ); @@ -378,7 +393,7 @@ async function sendMonitoringMailsBatched( async function sendOnlineAgainMails(startTime: Moment): Promise { await sendMonitoringMailsBatched( 'online again', - 'monitoring-online-again', + MailType.MONITORING_ONLINE_AGAIN, async (): Promise => await db.all( 'SELECT * FROM node_state ' + 'WHERE modified_at < ? AND state = ? AND last_status_mail_type IN (' + @@ -395,10 +410,11 @@ async function sendOnlineAgainMails(startTime: Moment): Promise { ); } -async function sendOfflineMails(startTime: Moment, mailNumber: number): Promise { +async function sendOfflineMails(startTime: Moment, mailType: MailType): Promise { + const mailNumber = parseInteger(mailType.split("-")[2]); await sendMonitoringMailsBatched( 'offline ' + mailNumber, - 'monitoring-offline-' + mailNumber, + mailType, async (): Promise => { const previousType = mailNumber === 1 ? 'monitoring-online-again' : ('monitoring-offline-' + (mailNumber - 1)); @@ -556,12 +572,12 @@ export async function getAll(restParams: RestParams): Promise<{ total: number, m const where = Resources.whereCondition(restParams, filterFields); - const row = await db.get( + const row = await db.get<{ total: number }>( 'SELECT count(*) AS total FROM node_state WHERE ' + where.query, _.concat([], where.params), ); - const total = row.total; + const total = row?.total || 0; const filter = Resources.filterClause( restParams, @@ -578,7 +594,7 @@ export async function getAll(restParams: RestParams): Promise<{ total: number, m return {monitoringStates, total}; } -export async function getByMacs(macs: MAC[]): Promise> { +export async function getByMacs(macs: MAC[]): Promise> { if (_.isEmpty(macs)) { return {}; } @@ -588,13 +604,22 @@ export async function getByMacs(macs: MAC[]): Promise( 'SELECT * FROM node_state WHERE ' + inCondition.query, _.concat([], inCondition.params), ); for (const row of rows) { - nodeStateByMac[row.mac] = row; + const onlineState = row.state; + if (!isOnlineState(onlineState)) { + throw new Error(`Invalid online state in database: "${onlineState}"`); + } + + nodeStateByMac[row.mac] = { + site: row.site || "" as Site, // FIXME: Handle this + domain: row.domain || "" as Domain, // FIXME: Handle this + state: onlineState, + }; } } @@ -603,7 +628,7 @@ export async function getByMacs(macs: MAC[]): Promise { const {node, nodeSecrets} = await NodeService.getNodeDataWithSecretsByMonitoringToken(token); - if (!node.monitoring || !nodeSecrets.monitoringToken || !equal(nodeSecrets.monitoringToken, token)) { + if (!node.monitoring || !nodeSecrets.monitoringToken || nodeSecrets.monitoringToken !== token) { throw {data: 'Invalid token.', type: ErrorTypes.badRequest}; } @@ -619,7 +644,7 @@ export async function confirm(token: MonitoringToken): Promise { export async function disable(token: MonitoringToken): Promise { const {node, nodeSecrets} = await NodeService.getNodeDataWithSecretsByMonitoringToken(token); - if (!node.monitoring || !nodeSecrets.monitoringToken || !equal(nodeSecrets.monitoringToken, token)) { + if (!node.monitoring || !nodeSecrets.monitoringToken || nodeSecrets.monitoringToken !== token) { throw {data: 'Invalid token.', type: ErrorTypes.badRequest}; } @@ -654,14 +679,18 @@ export async function sendMonitoringMails(): Promise { .error('Error sending "online again" mails.', error); } - for (let mailNumber = 1; mailNumber <= 3; mailNumber++) { + for (const mailType of [ + MailType.MONITORING_OFFLINE_1, + MailType.MONITORING_OFFLINE_2, + MailType.MONITORING_OFFLINE_3, + ]) { try { - await sendOfflineMails(startTime, mailNumber); + await sendOfflineMails(startTime, mailType); } catch (error) { // only logging an continuing with next type Logger .tag('monitoring', 'mail-sending') - .error('Error sending "offline ' + mailNumber + '" mails.', error); + .error('Error sending "' + mailType + '" mails.', error); } } } @@ -767,7 +796,7 @@ async function deleteNeverOnlineNodesBefore(deleteBefore: UnixTimestampSeconds): } async function deleteNodesOfflineSinceBefore(deleteBefore: UnixTimestampSeconds): Promise { - const rows = await db.all( + const rows = await db.all( 'SELECT * FROM node_state WHERE state = ? AND last_seen < ?', [ 'OFFLINE', diff --git a/server/services/nodeService.ts b/server/services/nodeService.ts index 32a1749..0842df9 100644 --- a/server/services/nodeService.ts +++ b/server/services/nodeService.ts @@ -11,14 +11,18 @@ import * as MailService from "../services/mailService"; import {normalizeString} from "../utils/strings"; import {monitoringConfirmUrl, monitoringDisableUrl} from "../utils/urlBuilder"; import { + Coordinates, + EmailAddress, FastdKey, + Hostname, MAC, + MailType, MonitoringState, MonitoringToken, + Nickname, Node, NodeSecrets, NodeStatistics, - to, Token, toUnixTimestampSeconds, unhandledEnumField, @@ -60,18 +64,17 @@ enum LINE_PREFIX { const filenameParts = ['hostname', 'mac', 'key', 'token', 'monitoringToken']; -function generateToken(): Type { - return to(crypto.randomBytes(8).toString('hex')); +function generateToken(): Type { + return crypto.randomBytes(8).toString('hex') as Type; } function toNodeFilesPattern(filter: NodeFilter): string { const fields: (string | undefined)[] = [ filter.hostname, - filter.mac?.value, - filter.key?.value, - filter.token?.value, - filter.monitoringToken?.value, + filter.mac, + filter.key, + filter.token, + filter.monitoringToken, ]; const pattern = fields.map((value) => value || '*').join('@'); @@ -124,7 +127,7 @@ function isDuplicate(filter: NodeFilter, token: Token | null): boolean { return true; } - return parseNodeFilename(files[0]).token !== token.value; + return parseNodeFilename(files[0]).token !== token; } function checkNoDuplicates(token: Token | null, node: Node, nodeSecrets: NodeSecrets): void { @@ -169,9 +172,9 @@ function getNodeValue(prefix: LINE_PREFIX, node: Node, nodeSecrets: NodeSecrets) case LINE_PREFIX.COORDS: return node.coords || ""; case LINE_PREFIX.MAC: - return node.mac.value; + return node.mac; case LINE_PREFIX.TOKEN: - return node.token.value; + return node.token; case LINE_PREFIX.MONITORING: if (node.monitoring && node.monitoringConfirmed) { return "aktiv"; @@ -180,7 +183,7 @@ function getNodeValue(prefix: LINE_PREFIX, node: Node, nodeSecrets: NodeSecrets) } return ""; case LINE_PREFIX.MONITORING_TOKEN: - return nodeSecrets.monitoringToken?.value || ""; + return nodeSecrets.monitoringToken || ""; default: return unhandledEnumField(prefix); } @@ -255,13 +258,13 @@ async function deleteNodeFile(token: Token): Promise { } class NodeBuilder { - public token: Token = to(""); // FIXME: Either make token optional in Node or handle this! - public nickname: string = ""; - public email: string = ""; - public hostname: string = ""; // FIXME: Either make hostname optional in Node or handle this! - public coords?: string; + public token: Token = "" as Token; // FIXME: Either make token optional in Node or handle this! + public nickname: Nickname = "" as Nickname; + public email: EmailAddress = "" as EmailAddress; + public hostname: Hostname = "" as Hostname; // FIXME: Either make hostname optional in Node or handle this! + public coords?: Coordinates; public key?: FastdKey; - public mac: MAC = to(""); // FIXME: Either make mac optional in Node or handle this! + public mac: MAC = "" as MAC; // FIXME: Either make mac optional in Node or handle this! public monitoring: boolean = false; public monitoringConfirmed: boolean = false; public monitoringState: MonitoringState = MonitoringState.DISABLED; @@ -291,22 +294,22 @@ class NodeBuilder { function setNodeValue(prefix: LINE_PREFIX, node: NodeBuilder, nodeSecrets: NodeSecrets, value: string) { switch (prefix) { case LINE_PREFIX.HOSTNAME: - node.hostname = value; + node.hostname = value as Hostname; break; case LINE_PREFIX.NICKNAME: - node.nickname = value; + node.nickname = value as Nickname; break; case LINE_PREFIX.EMAIL: - node.email = value; + node.email = value as EmailAddress; break; case LINE_PREFIX.COORDS: - node.coords = value; + node.coords = value as Coordinates; break; case LINE_PREFIX.MAC: - node.mac = to(value); + node.mac = value as MAC; break; case LINE_PREFIX.TOKEN: - node.token = to(value); + node.token = value as Token; break; case LINE_PREFIX.MONITORING: const active = value === 'aktiv'; @@ -317,7 +320,7 @@ function setNodeValue(prefix: LINE_PREFIX, node: NodeBuilder, nodeSecrets: NodeS active ? MonitoringState.ACTIVE : (pending ? MonitoringState.PENDING : MonitoringState.DISABLED); break; case LINE_PREFIX.MONITORING_TOKEN: - nodeSecrets.monitoringToken = to(value); + nodeSecrets.monitoringToken = value as MonitoringToken; break; default: return unhandledEnumField(prefix); @@ -340,7 +343,7 @@ async function parseNodeFile(file: string): Promise<{ node: Node, nodeSecrets: N for (const line of lines) { if (line.substring(0, 5) === 'key "') { - node.key = to(normalizeString(line.split('"')[1])); + node.key = normalizeString(line.split('"')[1]) as FastdKey; } else { for (const prefix of Object.values(LINE_PREFIX)) { if (line.substring(0, prefix.length) === prefix) { @@ -393,7 +396,7 @@ async function sendMonitoringConfirmationMail(node: Node, nodeSecrets: NodeSecre await MailService.enqueue( config.server.email.from, node.nickname + ' <' + node.email + '>', - 'monitoring-confirmation', + MailType.MONITORING_CONFIRMATION, { node: node, confirmUrl: confirmUrl, diff --git a/server/types/config.ts b/server/types/config.ts index d2cc41d..d25ef3d 100644 --- a/server/types/config.ts +++ b/server/types/config.ts @@ -1,32 +1,17 @@ import {ArrayField, Field, RawJsonField} from "sparkson" -import {ClientConfig, to} from "./shared"; +import {ClientConfig, JSONObject, Url} from "./shared"; // TODO: Replace string types by more specific types like URL, Password, etc. -export type Username = { - value: string; - readonly __tag: unique symbol -}; -export type CleartextPassword = { - value: string; - readonly __tag: unique symbol -}; -export type PasswordHash = { - value: string; - readonly __tag: unique symbol -}; +export type Username = string & { readonly __tag: unique symbol }; +export type CleartextPassword = string & { readonly __tag: unique symbol }; +export type PasswordHash = string & { readonly __tag: unique symbol }; export class UsersConfig { - public username: Username; - public passwordHash: PasswordHash; - constructor( - @Field("user") username: string, - @Field("passwordHash") passwordHash: string, - ) { - this.username = to(username); - this.passwordHash = to(passwordHash); - } + @Field("user") public username: Username, + @Field("passwordHash") public passwordHash: PasswordHash, + ) {} } export class LoggingConfig { @@ -49,19 +34,19 @@ export class EmailConfig { @Field("from") public from: string, // For details see: https://nodemailer.com/2-0-0-beta/setup-smtp/ - @RawJsonField("smtp") public smtp: any, // TODO: Better types! + @RawJsonField("smtp") public smtp: JSONObject, ) {} } export class ServerMapConfig { constructor( - @ArrayField("nodesJsonUrl", String) public nodesJsonUrl: string[], + @ArrayField("nodesJsonUrl", String) public nodesJsonUrl: Url[], ) {} } export class ServerConfig { constructor( - @Field("baseUrl") public baseUrl: string, + @Field("baseUrl") public baseUrl: Url, @Field("port") public port: number, @Field("databaseFile") public databaseFile: string, diff --git a/server/types/index.ts b/server/types/index.ts index dc82434..d2d62ba 100644 --- a/server/types/index.ts +++ b/server/types/index.ts @@ -1,4 +1,4 @@ -import {Domain, MonitoringToken, OnlineState, Site} from "./shared"; +import {Domain, EmailAddress, JSONObject, MonitoringToken, OnlineState, Site, toIsEnum} from "./shared"; export * from "./config"; export * from "./logger"; @@ -15,15 +15,24 @@ export type NodeSecrets = { monitoringToken?: MonitoringToken, }; -export type MailId = string; -export type MailData = any; -export type MailType = string; +export type MailId = number & { readonly __tag: unique symbol }; +export type MailData = JSONObject; + +export enum MailType { + MONITORING_OFFLINE_1 = "monitoring-offline-1", + MONITORING_OFFLINE_2 = "monitoring-offline-2", + MONITORING_OFFLINE_3 = "monitoring-offline-3", + MONITORING_ONLINE_AGAIN = "monitoring-online-again", + MONITORING_CONFIRMATION = "monitoring-confirmation", +} + +export const isMailType = toIsEnum(MailType); export interface Mail { id: MailId, email: MailType, - sender: string, - recipient: string, + sender: EmailAddress, + recipient: EmailAddress, data: MailData, failures: number, } diff --git a/server/types/shared.ts b/server/types/shared.ts index dae75c2..7aa2081 100644 --- a/server/types/shared.ts +++ b/server/types/shared.ts @@ -1,8 +1,60 @@ import {ArrayField, Field, RawJsonField} from "sparkson"; +import exp from "constants"; // Types shared with the client. export type TypeGuard = (arg: unknown) => arg is T; +export function parseJSON(str: string): JSONValue { + const json = JSON.parse(str); + if (!isJSONValue(json)) { + throw new Error("Invalid JSON returned. Should never happen."); + } + return json; +} + +export type JSONValue = + | null + | string + | number + | boolean + | JSONObject + | JSONArray; + +export function isJSONValue(arg: unknown): arg is JSONValue { + return ( + arg === null || + isString(arg) || + isNumber(arg) || + isBoolean(arg) || + isJSONObject(arg) || + isJSONArray(arg) + ); +} + +export interface JSONObject { + [x: string]: JSONValue; +} + +export function isJSONObject(arg: unknown): arg is JSONObject { + if (!isObject(arg)) { + return false; + } + + const obj = arg as object; + for (const [key, value] of Object.entries(obj)) { + if (!isString(key) || !isJSONValue(value)) { + return false; + } + } + + return true; +} + +export interface JSONArray extends Array { +} + +export const isJSONArray = toIsArray(isJSONValue); + export type EnumValue = E[keyof E]; export type EnumTypeGuard = TypeGuard>; @@ -10,33 +62,10 @@ export function unhandledEnumField(field: never): never { throw new Error(`Unhandled enum field: ${field}`); } -export function to(value: Type['value']): Type { - return value as any as Type; -} - -export function lift2(callback: (a: Type["value"], b: Type["value"]) => Result): (newtype1: Type, newtype2: Type) => Result { - return (a, b) => callback(a.value, b.value); -} - -export function equal(a: Type, b: Type): boolean { - return lift2((a, b) => a === b)(a, b); -} - export function isObject(arg: unknown): arg is object { return arg !== null && typeof arg === "object"; } -export function toIsNewtype(isValue: TypeGuard): TypeGuard { - // TODO: Add validation pattern. - return (arg: unknown): arg is Type => { - if (!isObject(arg)) { - return false; - } - const newtype = arg as Type; - return isValue(newtype.value); - } -} - export function isArray(arg: unknown, isT: TypeGuard): arg is Array { if (!Array.isArray(arg)) { return false; @@ -77,11 +106,15 @@ export function isOptional(arg: unknown, isT: TypeGuard): arg is (T | unde return arg === undefined || isT(arg); } -export type Version = string; +export type Url = string & { readonly __tag: unique symbol }; +export const isUrl = isString; -// Should be good enough for now. +export type Version = string & { readonly __tag: unique symbol }; export const isVersion = isString; +export type EmailAddress = string & { readonly __tag: unique symbol }; +export const isEmailAddress = isString; + export type NodeStatistics = { registered: number; withVPN: number; @@ -119,10 +152,11 @@ export class CommunityConfig { constructor( @Field("name") public name: string, @Field("domain") public domain: string, - @Field("contactEmail") public contactEmail: string, - @ArrayField("sites", String) public sites: string[], - @ArrayField("domains", String) public domains: string[], - ) {} + @Field("contactEmail") public contactEmail: EmailAddress, + @ArrayField("sites", String) public sites: Site[], + @ArrayField("domains", String) public domains: Domain[], + ) { + } } export function isCommunityConfig(arg: unknown): arg is CommunityConfig { @@ -133,17 +167,18 @@ export function isCommunityConfig(arg: unknown): arg is CommunityConfig { return ( isString(cfg.name) && isString(cfg.domain) && - isString(cfg.contactEmail) && - isArray(cfg.sites, isString) && - isArray(cfg.domains, isString) + isEmailAddress(cfg.contactEmail) && + isArray(cfg.sites, isSite) && + isArray(cfg.domains, isDomain) ); } export class LegalConfig { constructor( - @Field("privacyUrl", true) public privacyUrl?: string, - @Field("imprintUrl", true) public imprintUrl?: string, - ) {} + @Field("privacyUrl", true) public privacyUrl?: Url, + @Field("imprintUrl", true) public imprintUrl?: Url, + ) { + } } export function isLegalConfig(arg: unknown): arg is LegalConfig { @@ -152,15 +187,16 @@ export function isLegalConfig(arg: unknown): arg is LegalConfig { } const cfg = arg as LegalConfig; return ( - isOptional(cfg.privacyUrl, isString) && - isOptional(cfg.imprintUrl, isString) + isOptional(cfg.privacyUrl, isUrl) && + isOptional(cfg.imprintUrl, isUrl) ); } export class ClientMapConfig { constructor( - @Field("mapUrl") public mapUrl: string, - ) {} + @Field("mapUrl") public mapUrl: Url, + ) { + } } export function isClientMapConfig(arg: unknown): arg is ClientMapConfig { @@ -168,13 +204,14 @@ export function isClientMapConfig(arg: unknown): arg is ClientMapConfig { return false; } const cfg = arg as ClientMapConfig; - return isString(cfg.mapUrl); + return isUrl(cfg.mapUrl); } export class MonitoringConfig { constructor( @Field("enabled") public enabled: boolean, - ) {} + ) { + } } export function isMonitoringConfig(arg: unknown): arg is MonitoringConfig { @@ -185,43 +222,45 @@ export function isMonitoringConfig(arg: unknown): arg is MonitoringConfig { return isBoolean(cfg.enabled); } -export class Coords { +export class CoordinatesConfig { constructor( @Field("lat") public lat: number, @Field("lng") public lng: number, - ) {} + ) { + } } -export function isCoords(arg: unknown): arg is Coords { +export function isCoordinatesConfig(arg: unknown): arg is CoordinatesConfig { if (!isObject(arg)) { return false; } - const coords = arg as Coords; + const coords = arg as CoordinatesConfig; return ( isNumber(coords.lat) && isNumber(coords.lng) ); } -export class CoordsSelectorConfig { +export class CoordinatesSelectorConfig { constructor( @Field("lat") public lat: number, @Field("lng") public lng: number, @Field("defaultZoom") public defaultZoom: number, - @RawJsonField("layers") public layers: any, // TODO: Better types! - ) {} + @RawJsonField("layers") public layers: JSONObject, + ) { + } } -export function isCoordsSelectorConfig(arg: unknown): arg is CoordsSelectorConfig { +export function isCoordinatesSelectorConfig(arg: unknown): arg is CoordinatesSelectorConfig { if (!isObject(arg)) { return false; } - const cfg = arg as CoordsSelectorConfig; + const cfg = arg as CoordinatesSelectorConfig; return ( isNumber(cfg.lat) && isNumber(cfg.lng) && isNumber(cfg.defaultZoom) && - isObject(cfg.layers) // TODO: Better types! + isJSONObject(cfg.layers) ); } @@ -229,8 +268,9 @@ export class OtherCommunityInfoConfig { constructor( @Field("showInfo") public showInfo: boolean, @Field("showBorderForDebugging") public showBorderForDebugging: boolean, - @ArrayField("localCommunityPolygon", Coords) public localCommunityPolygon: Coords[], - ) {} + @ArrayField("localCommunityPolygon", CoordinatesConfig) public localCommunityPolygon: CoordinatesConfig[], + ) { + } } export function isOtherCommunityInfoConfig(arg: unknown): arg is OtherCommunityInfoConfig { @@ -241,7 +281,7 @@ export function isOtherCommunityInfoConfig(arg: unknown): arg is OtherCommunityI return ( isBoolean(cfg.showInfo) && isBoolean(cfg.showBorderForDebugging) && - isArray(cfg.localCommunityPolygon, isCoords) + isArray(cfg.localCommunityPolygon, isCoordinatesConfig) ); } @@ -251,7 +291,7 @@ export class ClientConfig { @Field("legal") public legal: LegalConfig, @Field("map") public map: ClientMapConfig, @Field("monitoring") public monitoring: MonitoringConfig, - @Field("coordsSelector") public coordsSelector: CoordsSelectorConfig, + @Field("coordsSelector") public coordsSelector: CoordinatesSelectorConfig, @Field("otherCommunityInfo") public otherCommunityInfo: OtherCommunityInfoConfig, @Field("rootPath", true, undefined, "/") public rootPath: string, ) { @@ -268,42 +308,33 @@ export function isClientConfig(arg: unknown): arg is ClientConfig { isLegalConfig(cfg.legal) && isClientMapConfig(cfg.map) && isMonitoringConfig(cfg.monitoring) && - isCoordsSelectorConfig(cfg.coordsSelector) && + isCoordinatesSelectorConfig(cfg.coordsSelector) && isOtherCommunityInfoConfig(cfg.otherCommunityInfo) && isString(cfg.rootPath) ); } // TODO: Token type. -export type Token = { - value: string; - readonly __tag: unique symbol -}; -export const isToken = toIsNewtype(isString); +export type Token = string & { readonly __tag: unique symbol }; +export const isToken = isString; -export type FastdKey = { - value: string; - readonly __tag: unique symbol -}; -export const isFastdKey = toIsNewtype(isString); +export type FastdKey = string & { readonly __tag: unique symbol }; +export const isFastdKey = isString; -export type MAC = { - value: string; - readonly __tag: unique symbol -}; -export const isMAC = toIsNewtype(isString); +export type MAC = string & { readonly __tag: unique symbol }; +export const isMAC = isString; export type UnixTimestampSeconds = number & { readonly __tag: unique symbol }; +export const isUnixTimestampSeconds = isNumber; + export type UnixTimestampMilliseconds = number & { readonly __tag: unique symbol }; +export const isUnixTimestampMilliseconds = isNumber; export function toUnixTimestampSeconds(ms: UnixTimestampMilliseconds): UnixTimestampSeconds { return Math.floor(ms) as UnixTimestampSeconds; } -export type MonitoringToken = { - value: string; - readonly __tag: unique symbol -}; +export type MonitoringToken = string & { readonly __tag: unique symbol }; export enum MonitoringState { ACTIVE = "active", @@ -313,25 +344,31 @@ export enum MonitoringState { export const isMonitoringState = toIsEnum(MonitoringState); -export type NodeId = { - value: string; - readonly __tag: unique symbol -}; +export type NodeId = string & { readonly __tag: unique symbol }; + +export type Hostname = string & { readonly __tag: unique symbol }; +export const isHostname = isString; + +export type Nickname = string & { readonly __tag: unique symbol }; +export const isNickname = isString; + +export type Coordinates = string & { readonly __tag: unique symbol }; +export const isCoordinates = isString; // TODO: More Newtypes -export interface Node { +export type Node = { token: Token; - nickname: string; - email: string; - hostname: string; - coords?: string; // TODO: Use object with longitude and latitude. + nickname: Nickname; + email: EmailAddress; + hostname: Hostname; + coords?: Coordinates; key?: FastdKey; mac: MAC; monitoring: boolean; monitoringConfirmed: boolean; monitoringState: MonitoringState; modifiedAt: UnixTimestampSeconds; -} +}; export function isNode(arg: unknown): arg is Node { if (!isObject(arg)) { @@ -340,16 +377,16 @@ export function isNode(arg: unknown): arg is Node { const node = arg as Node; return ( isToken(node.token) && - isString(node.nickname) && - isString(node.email) && - isString(node.hostname) && - isOptional(node.coords, isString) && + isNickname(node.nickname) && + isEmailAddress(node.email) && + isHostname(node.hostname) && + isOptional(node.coords, isCoordinates) && isOptional(node.key, isFastdKey) && isMAC(node.mac) && isBoolean(node.monitoring) && isBoolean(node.monitoringConfirmed) && isMonitoringState(node.monitoringState) && - isNumber(node.modifiedAt) + isUnixTimestampSeconds(node.modifiedAt) ); } @@ -360,17 +397,11 @@ export enum OnlineState { export const isOnlineState = toIsEnum(OnlineState); -export type Site = { - value: string; - readonly __tag: unique symbol -}; -export const isSite = toIsNewtype(isString); +export type Site = string & { readonly __tag: unique symbol }; +export const isSite = isString; -export type Domain = { - value: string; - readonly __tag: unique symbol -}; -export const isDomain = toIsNewtype(isString); +export type Domain = string & { readonly __tag: unique symbol }; +export const isDomain = isString; export interface EnhancedNode extends Node { site?: Site, diff --git a/server/utils/strings.ts b/server/utils/strings.ts index 0c9c218..78f126a 100644 --- a/server/utils/strings.ts +++ b/server/utils/strings.ts @@ -17,7 +17,11 @@ export function normalizeMac (mac: string): string { return macParts.join(':'); } -export function parseInteger (str: string): number | undefined { +export function parseInteger (str: string): number { const parsed = _.parseInt(str, 10); - return parsed.toString() === str ? parsed : undefined; + if (parsed.toString() === str) { + return parsed; + } else { + throw new SyntaxError(`String does not represent a valid integer: "${str}"`); + } } diff --git a/server/utils/urlBuilder.ts b/server/utils/urlBuilder.ts index dc244aa..79b037e 100644 --- a/server/utils/urlBuilder.ts +++ b/server/utils/urlBuilder.ts @@ -1,11 +1,9 @@ import _ from "lodash" import {config} from "../config" -import {MonitoringToken} from "../types" +import {MonitoringToken, Url} from "../types" -// TODO: Typed URLs - -function formUrl(route: string, queryParams?: { [key: string]: string }): string { - let url = config.server.baseUrl; +function formUrl(route: string, queryParams?: { [key: string]: string }): Url { + let url = config.server.baseUrl as string; if (route || queryParams) { url += '/#/'; } @@ -24,17 +22,17 @@ function formUrl(route: string, queryParams?: { [key: string]: string }): string '&' ); } - return url; + return url as Url; } -export function editNodeUrl(): string { +export function editNodeUrl(): Url { return formUrl('update'); } -export function monitoringConfirmUrl(monitoringToken: MonitoringToken): string { - return formUrl('monitoring/confirm', {token: monitoringToken.value}); +export function monitoringConfirmUrl(monitoringToken: MonitoringToken): Url { + return formUrl('monitoring/confirm', {token: monitoringToken}); } -export function monitoringDisableUrl(monitoringToken: MonitoringToken): string { - return formUrl('monitoring/disable', {token: monitoringToken.value}); +export function monitoringDisableUrl(monitoringToken: MonitoringToken): Url { + return formUrl('monitoring/disable', {token: monitoringToken}); } diff --git a/yarn.lock b/yarn.lock index a770074..2608692 100644 --- a/yarn.lock +++ b/yarn.lock @@ -368,6 +368,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -693,6 +698,22 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@selderee/plugin-htmlparser2@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d" @@ -730,6 +751,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/async@^3.2.15": version "3.2.15" resolved "https://registry.yarnpkg.com/@types/async/-/async-3.2.15.tgz#26d4768fdda0e466f18d6c9918ca28cc89a4e1fe" @@ -1057,13 +1083,30 @@ admin-config@^0.12.4: resolved "https://registry.yarnpkg.com/admin-config/-/admin-config-0.12.4.tgz#b0a324785c0e5b8ec489148ac6d1402ae851dac3" integrity sha512-vHiZIDK4QysLXKOGrRJ9IZlUfJuvSUQUELJz4oaj0o70KD7v+Fsate1dMVQd+1LS1VbN73BZe1aEPw8mEjeDcw== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" +agentkeepalive@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv@^6.12.3: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1213,11 +1256,6 @@ applause@^2.0.0: cson-parser "^4.0.8" js-yaml "^4.0.0" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - "aproba@^1.0.3 || ^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -1243,13 +1281,13 @@ are-we-there-yet@^2.0.0: delegates "^1.0.0" readable-stream "^3.6.0" -are-we-there-yet@~1.1.2: - version "1.1.7" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" - integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== +are-we-there-yet@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d" + integrity sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw== dependencies: delegates "^1.0.0" - readable-stream "^2.0.6" + readable-stream "^3.6.0" argparse@^1.0.7: version "1.0.10" @@ -1867,6 +1905,30 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2049,11 +2111,6 @@ chokidar@^3: optionalDependencies: fsevents "~2.3.2" -chownr@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -2093,6 +2150,11 @@ clean-css@^5.0.1: dependencies: source-map "~0.6.0" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + cli@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14" @@ -2131,11 +2193,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - codemirror@^5.2.0: version "5.65.3" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.3.tgz#2d029930d5a293bc5fb96ceea64654803c0d4ac7" @@ -2183,7 +2240,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.2: +color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== @@ -2310,7 +2367,7 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -2523,14 +2580,14 @@ debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.2.6: +debug@^3.1.0: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -2672,7 +2729,7 @@ depd@2.0.0, depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@~1.1.2: +depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -2687,11 +2744,6 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -2901,6 +2953,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2918,11 +2977,21 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + eol@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -3604,13 +3673,6 @@ fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3663,19 +3725,19 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" gaze@^1.1.0: version "1.1.3" @@ -3956,7 +4018,7 @@ got@^8.3.1: url-parse-lax "^3.0.0" url-to-options "^1.0.1" -graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: +graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -4349,7 +4411,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0, has-unicode@^2.0.1: +has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -4503,6 +4565,11 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== +http-cache-semantics@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-errors@2.0.0, http-errors@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -4529,6 +4596,15 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy@~1.11.0: version "1.11.3" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.11.3.tgz#1915dc888751e2a6bf3c2abfcb1808fa86c72353" @@ -4574,25 +4650,32 @@ humane-js@^3.2.2: resolved "https://registry.yarnpkg.com/humane-js/-/humane-js-3.2.2.tgz#b762af5f8b246f3fd6f455eb131c252bd5c00a8c" integrity sha1-t2KvX4skbz/W9FXrExwlK9XACow= -iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@0.4.24, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore-walk@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" - integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== - dependencies: - minimatch "^3.0.4" - ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" @@ -4694,6 +4777,11 @@ indexof@0.0.1: resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflection@~1.4.0: version "1.4.2" resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.4.2.tgz#8bddf6ede66014cd33293dfe76e18a77f53d4a3a" @@ -4722,7 +4810,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -4749,6 +4837,11 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -4899,13 +4992,6 @@ is-finite@^1.0.0, is-finite@^1.0.1: resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -4942,6 +5028,11 @@ is-jpg@^2.0.0: resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-2.0.0.tgz#2e1997fa6e9166eaac0242daae443403e4ef1d97" integrity sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc= +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -5988,6 +6079,28 @@ make-error@1.x: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -6160,7 +6273,7 @@ minimist@^0.2.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455" integrity sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg== -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -6170,29 +6283,53 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" integrity sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw== -minipass@^2.6.0, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" + minipass "^3.0.0" -minipass@^3.0.0: +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.3.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== dependencies: yallist "^4.0.0" -minizlib@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - -minizlib@^2.1.1: +minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -6208,14 +6345,14 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.x, mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" -mkdirp@^1.0.3, mkdirp@~1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -6256,7 +6393,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6272,11 +6409,6 @@ multimatch@^4.0.0: arrify "^2.0.1" minimatch "^3.0.4" -nan@^2.12.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -6309,16 +6441,7 @@ nearley@^2.20.1: railroad-diagrams "^1.0.0" randexp "0.4.6" -needle@^2.2.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" - integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -negotiator@0.6.3: +negotiator@0.6.3, negotiator@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -6415,6 +6538,11 @@ node-addon-api@^3.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-addon-api@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" + integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== + node-cron@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.1.tgz#6a9d5a71513c7ee1eb9e64a673f6730084561d87" @@ -6427,6 +6555,22 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-http2@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/node-http2/-/node-http2-4.0.1.tgz#164ff53b5dd22c84f0af142b877c5eaeb6809959" @@ -6475,22 +6619,6 @@ node-libs-browser@^0.5.0: util "~0.10.3" vm-browserify "0.0.4" -node-pre-gyp@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" - integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - node-releases@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" @@ -6523,14 +6651,6 @@ nopt@3.x, nopt@~3.0.6: dependencies: abbrev "1" -nopt@^4.0.1, nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -6538,6 +6658,14 @@ nopt@^5.0.0: dependencies: abbrev "1" +nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -6562,13 +6690,6 @@ normalize-url@2.0.1: query-string "^5.0.1" sort-keys "^2.0.0" -npm-bundled@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" - integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== - dependencies: - npm-normalize-package-bin "^1.0.1" - npm-conf@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -6577,20 +6698,6 @@ npm-conf@^1.1.0: config-chain "^1.1.11" pify "^3.0.0" -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -6605,16 +6712,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" @@ -6625,6 +6722,16 @@ npmlog@^5.0.1: gauge "^3.0.0" set-blocking "^2.0.0" +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + nprogress@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" @@ -6952,6 +7059,13 @@ p-map@^1.2.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-pipe@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" @@ -7334,6 +7448,19 @@ process@^0.11.0, process@~0.11.0: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -7602,16 +7729,6 @@ raw-body@~1.1.0: bytes "1" string_decoder "0.10" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-is@^18.0.0: version "18.1.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" @@ -7654,7 +7771,7 @@ readable-stream@^1.0.27-1, readable-stream@^1.1.13: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -7863,12 +7980,17 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -7899,7 +8021,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -7916,12 +8038,12 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4, sax@~1.2.4: +sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -8021,7 +8143,7 @@ serve-static@1.15.0, serve-static@^1.14.1: parseurl "~1.3.3" send "0.18.0" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -8124,6 +8246,11 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -8154,6 +8281,23 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" + integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -8287,27 +8431,21 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sql-template-strings@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/sql-template-strings/-/sql-template-strings-2.2.2.tgz#3f11508a25addfce217a3042a9d300c3193b96ff" - integrity sha1-PxFQiiWt384hejBCqdMAwxk7lv8= - -sqlite3@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901" - integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg== +sqlite3@^5.0.9: + version "5.0.9" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.0.9.tgz#421b9c065480f1c589f89cabd6d0ea9575a69855" + integrity sha512-e2lEKevUF65UJu4IIuuFytgW7yNMkmCkfyn66jXWeb7OcdHvRo7nXhF+IQ25iW6x2grB0DyKdGCpx8Rd8EkA2Q== dependencies: - nan "^2.12.1" - node-pre-gyp "^0.11.0" - -sqlite@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/sqlite/-/sqlite-3.0.6.tgz#82d15bb45a1ca2a7ac8f7a1079f19dc9abe2ee75" - integrity sha512-5SW7HcN+s3TyqpsxOujXhQDCRSCgsxdiU0peT/Y9CT5T0rAsGLwtpXcMyQ7OzOPQ4YUZ5XiGlrwuuQbszr2xtw== - dependencies: - sqlite3 "^4.0.0" + "@mapbox/node-pre-gyp" "^1.0.0" + node-addon-api "^4.2.0" + tar "^6.1.11" optionalDependencies: - sql-template-strings "^2.2.2" + node-gyp "8.x" + +sqlite@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/sqlite/-/sqlite-4.1.1.tgz#e79c94d44e4381e30d5b3e885bfb9d802d757ecb" + integrity sha512-qssVl58Q4ytWabIK7e3lIjDuiXu0sq+M2foXFILrlJwpHisTgywQ5wDB5ImcOPMbuZHX3Q5gmlcDgX3m+VBfdw== squeak@^1.0.0: version "1.3.0" @@ -8333,6 +8471,13 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stable@^0.1.8, stable@~0.1.3, stable@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -8409,15 +8554,6 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -8481,7 +8617,7 @@ strip-ansi@^2.0.1: dependencies: ansi-regex "^1.0.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: +strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= @@ -8546,11 +8682,6 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - strip-outer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" @@ -8671,20 +8802,7 @@ tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" -tar@^4: - version "4.4.19" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" - integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== - dependencies: - chownr "^1.1.4" - fs-minipass "^1.2.7" - minipass "^2.9.0" - minizlib "^1.3.3" - mkdirp "^0.5.5" - safe-buffer "^5.2.1" - yallist "^3.1.1" - -tar@^6.1.11: +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -9034,6 +9152,20 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -9289,14 +9421,14 @@ which@^1.0.9, which@^1.1.1, which@^1.2.14, which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1, which@~2.0.2: +which@^2.0.1, which@^2.0.2, which@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -wide-align@^1.1.0, wide-align@^1.1.2: +wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== @@ -9403,11 +9535,6 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"