diff --git a/.erb/configs/webpack.config.base.ts b/.erb/configs/webpack.config.base.ts index 0ef00445af..1ee8c1611e 100644 --- a/.erb/configs/webpack.config.base.ts +++ b/.erb/configs/webpack.config.base.ts @@ -7,6 +7,10 @@ import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; import webpackPaths from './webpack.paths'; import { dependencies as externals } from '../../release/app/package.json'; +const isRenderer = + process.env.npm_lifecycle_script?.includes('webpack.config.renderer') ?? + false; + const configuration: webpack.Configuration = { externals: [...Object.keys(externals || {})], @@ -53,6 +57,26 @@ const configuration: webpack.Configuration = { new webpack.EnvironmentPlugin({ NODE_ENV: 'production', }), + + new webpack.IgnorePlugin({ + checkResource(resource, context) { + // Don't include stuff from the main folder or @main... in renderer and renderer folder in main folder + const exclude = isRenderer + ? resource.startsWith('@main') || resource.includes('main/') + : resource.startsWith('@renderer') || /renderer\//.test(resource); + + // Log if a file is excluded just fyi + if (!context.includes('node_modules') && exclude) + console.log( + `${ + isRenderer ? 'Renderer' : 'Main' + }: Resource ${resource}\n\tat context ${context}: ${ + exclude ? 'excluded' : 'included' + }`, + ); + return exclude; + }, + }), ], }; diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts index b11f6ba206..6d15100812 100644 --- a/.erb/configs/webpack.config.renderer.dev.ts +++ b/.erb/configs/webpack.config.renderer.dev.ts @@ -32,8 +32,8 @@ if ( ) { console.log( chalk.black.bgYellow.bold( - 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' - ) + 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"', + ), ); execSync('npm run postinstall'); } @@ -186,15 +186,14 @@ const configuration: webpack.Configuration = { shell: true, stdio: 'inherit', }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - .on('close', (code: number) => process.exit(code!)) + .on('close', (code: number) => process.exit(code)) .on('error', (spawnError) => console.error(spawnError)); console.log('Starting Main Process...'); let args = ['run', 'start:main']; if (process.env.MAIN_ARGS) { args = args.concat( - ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat() + ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat(), ); } spawn('npm', args, { @@ -203,8 +202,7 @@ const configuration: webpack.Configuration = { }) .on('close', (code: number) => { preloadProcess.kill(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - process.exit(code!); + process.exit(code); }) .on('error', (spawnError) => console.error(spawnError)); return middlewares; diff --git a/.erb/configs/webpack.paths.ts b/.erb/configs/webpack.paths.ts index e5ba573435..291a794a87 100644 --- a/.erb/configs/webpack.paths.ts +++ b/.erb/configs/webpack.paths.ts @@ -7,6 +7,7 @@ const dllPath = path.join(__dirname, '../dll'); const srcPath = path.join(rootPath, 'src'); const srcMainPath = path.join(srcPath, 'main'); const srcRendererPath = path.join(srcPath, 'renderer'); +const srcSharedPath = path.join(srcPath, 'shared'); const releasePath = path.join(rootPath, 'release'); const appPath = path.join(releasePath, 'app'); @@ -26,6 +27,7 @@ export default { srcPath, srcMainPath, srcRendererPath, + srcSharedPath, releasePath, appPath, appPackagePath, diff --git a/.erb/scripts/check-build-exists.ts b/.erb/scripts/check-build-exists.ts deleted file mode 100644 index ccb0ba8aba..0000000000 --- a/.erb/scripts/check-build-exists.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Check if the renderer and main bundles are built -import path from 'path'; -import chalk from 'chalk'; -import fs from 'fs'; -import webpackPaths from '../configs/webpack.paths'; - -const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); -const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); - -if (!fs.existsSync(mainPath)) { - throw new Error( - chalk.whiteBright.bgRed.bold( - 'The main process is not built yet. Build it by running "npm run build:main"' - ) - ); -} - -if (!fs.existsSync(rendererPath)) { - throw new Error( - chalk.whiteBright.bgRed.bold( - 'The renderer process is not built yet. Build it by running "npm run build:renderer"' - ) - ); -} diff --git a/.erb/scripts/check-native-dep.js b/.erb/scripts/check-native-dep.js index f5a2326808..6286981627 100644 --- a/.erb/scripts/check-native-dep.js +++ b/.erb/scripts/check-native-dep.js @@ -16,17 +16,17 @@ if (dependencies) { // because of a devDependency then that is okay. Warn when it is installed // because of a dependency const { dependencies: dependenciesObject } = JSON.parse( - execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() + execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(), ); const rootDependencies = Object.keys(dependenciesObject); const filteredRootDependencies = rootDependencies.filter((rootDependency) => - dependenciesKeys.includes(rootDependency) + dependenciesKeys.includes(rootDependency), ); if (filteredRootDependencies.length > 0) { const plural = filteredRootDependencies.length > 1; console.log(` ${chalk.whiteBright.bgYellow.bold( - 'Webpack does not work with native dependencies.' + 'Webpack does not work with native dependencies.', )} ${chalk.bold(filteredRootDependencies.join(', '))} ${ plural ? 'are native dependencies' : 'is a native dependency' @@ -34,16 +34,16 @@ ${chalk.bold(filteredRootDependencies.join(', '))} ${ First, uninstall the packages from "./package.json": ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} ${chalk.bold( - 'Then, instead of installing the package to the root "./package.json":' + 'Then, instead of installing the package to the root "./package.json":', )} ${chalk.whiteBright.bgRed.bold('npm install your-package')} ${chalk.bold('Install the package to "./release/app/package.json"')} ${chalk.whiteBright.bgGreen.bold( - 'cd ./release/app && npm install your-package' + 'cd ./release/app && npm install your-package', )} Read more about native dependencies at: ${chalk.bold( - 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' + 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure', )} `); process.exit(1); diff --git a/.erb/scripts/check-node-env.js b/.erb/scripts/check-node-env.js index 0e74b115e2..6bf674baa1 100644 --- a/.erb/scripts/check-node-env.js +++ b/.erb/scripts/check-node-env.js @@ -8,8 +8,8 @@ export default function checkNodeEnv(expectedEnv) { if (process.env.NODE_ENV !== expectedEnv) { console.log( chalk.whiteBright.bgRed.bold( - `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` - ) + `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`, + ), ); process.exit(2); } diff --git a/.erb/scripts/check-port-in-use.js b/.erb/scripts/check-port-in-use.js index 3ade730a71..9027d9fe9d 100644 --- a/.erb/scripts/check-port-in-use.js +++ b/.erb/scripts/check-port-in-use.js @@ -7,8 +7,8 @@ detectPort(port, (err, availablePort) => { if (port !== String(availablePort)) { throw new Error( chalk.whiteBright.bgRed.bold( - `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` - ) + `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`, + ), ); } else { process.exit(0); diff --git a/.erb/scripts/notarize.js b/.erb/scripts/notarize.js index 59ce058bc1..79bc54a2d9 100644 --- a/.erb/scripts/notarize.js +++ b/.erb/scripts/notarize.js @@ -14,7 +14,7 @@ exports.default = async function notarizeMacos(context) { if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { console.warn( - 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' + 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set', ); return; } diff --git a/.eslintrc.js b/.eslintrc.js index 408c21efa4..f93471cb98 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,35 @@ module.exports = { extends: 'erb', rules: { + // #region ERB rules + // A temporary hack related to IDE not resolving correct package.json 'import/no-extraneous-dependencies': 'off', 'import/no-unresolved': 'error', // Since React 17 and typescript 4.1 you can safely disable the rule 'react/react-in-jsx-scope': 'off', + + // #endregion + + // #region Our rules + + indent: 'off', + 'react/jsx-indent-props': ['warn', 2], + 'comma-dangle': ['error', 'always-multiline'], + 'prettier/prettier': ['warn', { tabWidth: 2, trailingComma: 'all' }], + 'no-console': 'off', + 'react/require-default-props': 'off', + 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], + 'jsx-a11y/label-has-associated-control': [ + 'error', + { + assert: 'either', + }, + ], + // This is already a Typescript rule, so we don't need it to be reported twice + '@typescript-eslint/no-unused-vars': 'off', + + // #endregion }, parserOptions: { ecmaVersion: 2020, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d296f0f14d..08b67d870e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,10 +42,9 @@ jobs: USE_HARD_LINKS: false GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - npm run package npm run lint - npm exec tsc npm test + npm run package # Enable tmate debugging of manually-triggered workflows if the input option was provided - name: Setup tmate session diff --git a/.gitignore b/.gitignore index da1343076a..3e9692bccf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ npm-debug.log.* *.css.d.ts *.sass.d.ts *.scss.d.ts + +# Extra VS Code workspaces +*.code-workspace diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af219892..c468e40a9f 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,8 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged +# Thanks to axmrnv for helping to fix the lint-staged output https://github.com/typicode/husky/issues/968#issuecomment-1176848345 +exec >/dev/tty 2>&1 + +# Can enable quiet mode to show nothing unless it has an error +npx lint-staged # -q diff --git a/.nvmrc b/.nvmrc index 99cdd8009c..aa18c6105c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.15.0 +16.17.1 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..7cad53588c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Coverage directory used by tools like istanbul +coverage +.eslintcache + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +# OSX +.DS_Store + +release/app/dist +release/build +.erb/dll + +.idea +npm-debug.log.* +*.css.d.ts +*.sass.d.ts +*.scss.d.ts + +# eslint ignores hidden directories by default: +# https://github.com/eslint/eslint/issues/8429 +!.erb diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..e16d110670 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "tabWidth": 2, + "trailingComma": "all", + "endOfLine": "lf", + "singleQuote": true, + "overrides": [ + { + "files": [".eslintrc"], + "options": { + "parser": "json" + } + } + ] +} diff --git a/README.md b/README.md index f5d7e7f3c3..12f14ab57b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # paranext-core -Backend webserver/extension host and frontend Electron client for Paranext +Electron client, extension host, and C# library for Paranext
@@ -53,6 +53,8 @@ Start the app in the `dev` environment: npm start ``` +After you run `npm start`, you can edit the Electron and frontend files, and they will hot reload. To edit C# files, you must stop the `npm start` process (or only close Paranext), run `npm run build:c-sharp`, and restart `npm start` (or if you only closed Paranext, make a trivial edit to `src/main/main.ts`, and save it to launch Paranext again). + ## Packaging for Production To package apps for the local platform: diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000000..6a41c5d633 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,27 @@ +// eslint-disable-next-line jest/no-jest-import +import type { Config } from 'jest'; +import { pathsToModuleNameMapper } from 'ts-jest'; +import { compilerOptions } from './tsconfig.json'; + +const config: Config = { + moduleDirectories: ['node_modules', 'release/app/node_modules', 'src'], + moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'], + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/.erb/mocks/fileMock.js', + '\\.(css|less|sass|scss)$': 'identity-obj-proxy', + ...pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '/src', + }), + }, + testEnvironment: 'jsdom', + testEnvironmentOptions: { + url: 'http://localhost/', + }, + testPathIgnorePatterns: ['release/app/dist'], + transform: { + '\\.(ts|tsx|js|jsx)$': 'ts-jest', + }, +}; + +export default config; diff --git a/package-lock.json b/package-lock.json index 63b57c5f9d..ceca18ad68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "electron-log": "^4.4.8", "electron-updater": "^5.2.3", "electron-window-state": "^5.0.3", + "memoize-one": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.4.0" + "react-router-dom": "^6.4.0", + "ws": "^8.12.0" }, "devDependencies": { "@electron/notarize": "^1.2.3", @@ -30,6 +32,7 @@ "@types/react-test-renderer": "^18.0.0", "@types/terser-webpack-plugin": "^5.0.4", "@types/webpack-bundle-analyzer": "^4.4.2", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/parser": "^5.33.1", "browserslist-config-erb": "^0.0.3", @@ -40,8 +43,8 @@ "css-loader": "^6.7.1", "css-minimizer-webpack-plugin": "^4.0.0", "detect-port": "^1.3.0", - "electron": "^20.0.2", - "electron-builder": "^23.3.3", + "electron": "^22.0.3", + "electron-builder": "^23.6.0", "electron-devtools-installer": "^3.2.0", "electronmon": "^2.0.2", "eslint": "^8.22.0", @@ -68,6 +71,7 @@ "react-refresh": "^0.14.0", "react-test-renderer": "^18.2.0", "rimraf": "^3.0.2", + "run-script-os": "^1.1.6", "sass": "^1.54.4", "sass-loader": "^13.0.2", "style-loader": "^3.3.1", @@ -75,6 +79,7 @@ "ts-jest": "^28.0.8", "ts-loader": "^9.3.1", "ts-node": "^10.9.1", + "tsconfig-paths": "^4.1.2", "tsconfig-paths-webpack-plugin": "^4.0.0", "typescript": "^4.7.4", "url-loader": "^4.1.1", @@ -2071,25 +2076,24 @@ } }, "node_modules/@electron/get": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.14.1.tgz", - "integrity": "sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", + "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", "dev": true, "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", - "got": "^9.6.0", + "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=8.6" + "node": ">=12" }, "optionalDependencies": { - "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1" + "global-agent": "^3.0.0" } }, "node_modules/@electron/get/node_modules/semver": { @@ -2200,72 +2204,6 @@ "node": ">= 12.13.0" } }, - "node_modules/@electron/rebuild/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@electron/rebuild/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/rebuild/node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/rebuild/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/rebuild/node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/@electron/rebuild/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -2280,52 +2218,6 @@ "node": ">=12" } }, - "node_modules/@electron/rebuild/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/rebuild/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/@electron/rebuild/node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, "node_modules/@electron/rebuild/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2338,69 +2230,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@electron/rebuild/node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/@electron/rebuild/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/rebuild/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/rebuild/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@electron/rebuild/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/rebuild/node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@electron/rebuild/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3196,6 +3025,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", "dev": true, "dependencies": { "mkdirp": "^1.0.4", @@ -3308,12 +3138,15 @@ "dev": true }, "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/@sinonjs/commons": { @@ -3614,44 +3447,35 @@ } }, "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "dev": true, "dependencies": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/@teamsupercell/typings-for-css-modules-loader": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.1.tgz", - "integrity": "sha512-8Dz/2awNbkrFHf3IpF8YGUPniXAZW/z7OTiosO+xucIU1+jVg/cT4uyGZ7z9cmAsnExsxq4igazxwgGBXVpUgA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.2.tgz", + "integrity": "sha512-3sqH2B4itcm5XgV1IHENt4NOaW7bOC1CwJr63vrdKWWyKVxNxtBM+ABVhJZYFCCVAwNy7ulA64z6HyQqw96m4A==", "dev": true, "dependencies": { "camelcase": "^5.3.1", - "loader-utils": "1.2.3", + "loader-utils": "^1.4.2", "schema-utils": "^2.0.1" }, "optionalDependencies": { "prettier": "*" } }, - "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -3661,13 +3485,13 @@ } }, "node_modules/@teamsupercell/typings-for-css-modules-loader/node_modules/loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "dev": true, "dependencies": { "big.js": "^5.2.2", - "emojis-list": "^2.0.0", + "emojis-list": "^3.0.0", "json5": "^1.0.1" }, "engines": { @@ -4098,7 +3922,7 @@ "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "node_modules/@types/keyv": { @@ -4117,9 +3941,9 @@ "dev": true }, "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "optional": true }, @@ -4315,9 +4139,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", "dev": true, "dependencies": { "@types/node": "*" @@ -5067,15 +4891,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5159,9 +4974,9 @@ "dev": true }, "node_modules/app-builder-lib": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.3.3.tgz", - "integrity": "sha512-m0+M53+HYMzqKxwNQZT143K7WwXEGUy9LY31l8dJphXx2P/FQod615mVbxHyqbDCG4J5bHdWm21qZ0e2DVY6CQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz", + "integrity": "sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==", "dev": true, "dependencies": { "@develar/schema-utils": "~2.6.5", @@ -5170,13 +4985,13 @@ "7zip-bin": "~5.1.1", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chromium-pickle-js": "^0.2.0", "debug": "^4.3.4", "ejs": "^3.1.7", "electron-osx-sign": "^0.6.0", - "electron-publish": "23.3.3", + "electron-publish": "23.6.0", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", @@ -5222,9 +5037,9 @@ } }, "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5377,6 +5192,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", "integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==", + "deprecated": "Please use @electron/asar moving forward. There is no API change, just a package name change", "dev": true, "dependencies": { "chromium-pickle-js": "^0.2.0", @@ -5799,40 +5615,6 @@ "dev": true, "optional": true }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5990,9 +5772,9 @@ "dev": true }, "node_modules/builder-util": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.3.3.tgz", - "integrity": "sha512-MJZlUiq2PY5hjYv9+XNaoYdsITqvLgRDoHSFg/4nzpInbNxNjLQOolL04Zsyp+hgfcbFvMC4h0KkR1CMPHLWbA==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz", + "integrity": "sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==", "dev": true, "dependencies": { "@types/debug": "^4.1.6", @@ -6000,7 +5782,7 @@ "7zip-bin": "~5.1.1", "app-builder-bin": "4.0.0", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "9.0.3", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "cross-spawn": "^7.0.3", "debug": "^4.3.4", @@ -6015,10 +5797,9 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.0.3.tgz", - "integrity": "sha512-SfG2wnyjpUbbdtpnqDpWwklujofC6GarGpvdWrEkg9p5AD/xJmTF2buTNaqs3qtsNBEVQDDjZz9xc2GGpVyMfA==", - "dev": true, + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", + "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -6072,9 +5853,9 @@ } }, "node_modules/cacache": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", - "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, "dependencies": { "@npmcli/fs": "^2.1.0", @@ -6094,7 +5875,7 @@ "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", - "unique-filename": "^1.1.1" + "unique-filename": "^2.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" @@ -6110,9 +5891,9 @@ } }, "node_modules/cacache/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -6129,18 +5910,18 @@ } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", "dev": true, "engines": { "node": ">=12" } }, "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6171,55 +5952,31 @@ } }, "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "dev": true, "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6411,18 +6168,6 @@ "node": ">=6" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -6436,9 +6181,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", "dev": true, "engines": { "node": ">=6" @@ -6477,7 +6222,7 @@ "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "engines": { "node": ">=0.8" @@ -6498,12 +6243,15 @@ } }, "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "dependencies": { "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/co": { @@ -6695,34 +6443,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "optional": true, - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -6924,15 +6644,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/css-declaration-sorter": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", @@ -7326,15 +7037,30 @@ "dev": true }, "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "dependencies": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/dedent": { @@ -7343,15 +7069,6 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7380,19 +7097,25 @@ } }, "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "dependencies": { "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/define-lazy-prop": { "version": "2.0.0", @@ -7575,14 +7298,14 @@ } }, "node_modules/dmg-builder": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.3.3.tgz", - "integrity": "sha512-ECwAjt+ZWyOvddrkDx1xRD6IVUCZb5SV6vSMHZd+Va3G2sUXHrnglR1cGDKRF4oYRQm8SYVrpLZKbi8npyDcAQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz", + "integrity": "sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==", "dev": true, "dependencies": { - "app-builder-lib": "23.3.3", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "app-builder-lib": "23.6.0", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "fs-extra": "^10.0.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" @@ -7774,18 +7497,6 @@ "tslib": "^2.0.3" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dotenv": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", @@ -7807,12 +7518,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7841,13 +7546,13 @@ } }, "node_modules/electron": { - "version": "20.0.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-20.0.2.tgz", - "integrity": "sha512-Op4nxSyXH0tXjhvWC+WDn9EI0gep5etPccainxu1A4wes+ZFQBMCBXxibotanJfG+WNW4RaOv88NArwHIsSmPw==", + "version": "22.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-22.0.3.tgz", + "integrity": "sha512-eETrJTINTzlXgQrnJSrKiF2Xdt5EHpxZ6Kk+WUjFCE0zUztdVm+hrngUecqhj8TPFlYScTANzPwRwUIjOChl+g==", "dev": true, "hasInstallScript": true, "dependencies": { - "@electron/get": "^1.14.1", + "@electron/get": "^2.0.0", "@types/node": "^16.11.26", "extract-zip": "^2.0.1" }, @@ -7855,27 +7560,27 @@ "electron": "cli.js" }, "engines": { - "node": ">= 10.17.0" + "node": ">= 12.20.55" } }, "node_modules/electron-builder": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.3.3.tgz", - "integrity": "sha512-mFYYdhoFPKevP6y5uaaF3dusmB2OtQ/HnwwpyOePeU7QDS0SEIAUokQsHUanAiJAZcBqtY7iyLBgX18QybdFFw==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz", + "integrity": "sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==", "dev": true, "dependencies": { "@types/yargs": "^17.0.1", - "app-builder-lib": "23.3.3", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "app-builder-lib": "23.6.0", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", - "dmg-builder": "23.3.3", + "dmg-builder": "23.6.0", "fs-extra": "^10.0.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", "read-config-file": "6.2.0", - "update-notifier": "^5.1.0", - "yargs": "^17.0.1" + "simple-update-notifier": "^1.0.7", + "yargs": "^17.5.1" }, "bin": { "electron-builder": "cli.js", @@ -7974,6 +7679,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz", "integrity": "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==", + "deprecated": "Please use @electron/osx-sign moving forward. Be aware the API is slightly different", "dev": true, "dependencies": { "bluebird": "^3.5.0", @@ -8019,14 +7725,14 @@ "dev": true }, "node_modules/electron-publish": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.3.3.tgz", - "integrity": "sha512-1dX17eE5xVXedTxjC+gjsP74oC0+sIHgqysp0ryTlF9+yfQUyXjBk6kcK+zhtBA2SsHMSglDtM+JPxDD/WpPTQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz", + "integrity": "sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==", "dev": true, "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "fs-extra": "^10.0.0", "lazy-val": "^1.0.5", @@ -8090,18 +7796,6 @@ "typed-emitter": "^2.1.0" } }, - "node_modules/electron-updater/node_modules/builder-util-runtime": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", - "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", - "dependencies": { - "debug": "^4.3.4", - "sax": "^1.2.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/electron-updater/node_modules/fs-extra": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", @@ -8400,15 +8094,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -9244,12 +8929,45 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/eslint-plugin-import/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/eslint-plugin-jest": { "version": "26.8.3", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.8.3.tgz", @@ -9759,21 +9477,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/extsprintf": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", @@ -9932,9 +9635,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10233,15 +9936,18 @@ } }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "dependencies": { "pump": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -10325,46 +10031,6 @@ "node": ">=10.0" } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/global-tunnel-ng": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", - "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", - "dev": true, - "optional": true, - "dependencies": { - "encodeurl": "^1.0.2", - "lodash": "^4.17.10", - "npm-conf": "^1.1.3", - "tunnel": "^0.0.6" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -10381,9 +10047,9 @@ } }, "node_modules/globalthis": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", - "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "optional": true, "dependencies": { @@ -10429,25 +10095,28 @@ "dev": true }, "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" }, "engines": { - "node": ">=8.6" + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, "node_modules/graceful-fs": { @@ -10569,15 +10238,6 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -10983,15 +10643,6 @@ "node": ">=8" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -11051,12 +10702,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -11246,26 +10891,10 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "engines": { "node": ">=8" @@ -11289,18 +10918,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11325,24 +10942,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -11443,12 +11042,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -11485,12 +11078,6 @@ "node": ">=8" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -12636,27 +12223,6 @@ } } }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", - "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -12670,9 +12236,9 @@ } }, "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, "node_modules/json-parse-even-better-errors": { @@ -12696,14 +12262,14 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "optional": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -12715,7 +12281,7 @@ "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -12756,12 +12322,12 @@ "integrity": "sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==" }, "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", "dev": true, "dependencies": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "node_modules/kind-of": { @@ -12806,18 +12372,6 @@ "language-subtag-registry": "~0.3.2" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", @@ -13185,9 +12739,9 @@ } }, "node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -13345,12 +12899,12 @@ } }, "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/lru-cache": { @@ -13470,9 +13024,9 @@ } }, "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", "dev": true, "engines": { "node": ">=12" @@ -13527,6 +13081,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/memory-fs": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", @@ -13756,9 +13315,9 @@ } }, "node_modules/minipass-fetch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.0.tgz", - "integrity": "sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, "dependencies": { "minipass": "^3.1.6", @@ -13903,9 +13462,9 @@ } }, "node_modules/node-abi": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz", - "integrity": "sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz", + "integrity": "sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -13940,16 +13499,16 @@ } }, "node_modules/node-gyp": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.1.0.tgz", - "integrity": "sha512-HkmN0ZpQJU7FLbJauJTHkHlSVAXlNGDAzH/VYFZGDOnFyn/Na3GlNJfkudmufOdS6/jNFhy88ObzL7ERz9es1g==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", "dev": true, "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.0.3", - "nopt": "^5.0.0", + "nopt": "^6.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", @@ -13960,13 +13519,13 @@ "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^12.22 || ^14.13 || >=16" + "node": "^12.13 || ^14.13 || >=16" } }, "node_modules/node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", "dev": true, "bin": { "node-gyp-build": "bin.js", @@ -13987,18 +13546,18 @@ "dev": true }, "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, "dependencies": { - "abbrev": "1" + "abbrev": "^1.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/normalize-path": { @@ -14011,26 +13570,15 @@ } }, "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "dev": true, - "optional": true, - "dependencies": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" + "node": ">=10" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-run-path": { @@ -14302,12 +13850,12 @@ } }, "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/p-limit": { @@ -14386,30 +13934,6 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -14565,16 +14089,6 @@ "node": ">=0.10" } }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -15056,18 +14570,6 @@ "postcss": "^8.2.15" } }, - "node_modules/postcss-normalize-url/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/postcss-normalize-whitespace": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", @@ -15189,15 +14691,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", @@ -15325,13 +14818,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true, - "optional": true - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -15379,18 +14865,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", @@ -15489,30 +14963,6 @@ "node": ">=0.10.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -15760,30 +15210,6 @@ "node": ">=4" } }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -15926,12 +15352,15 @@ } }, "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dev": true, "dependencies": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/restore-cursor": { @@ -16028,6 +15457,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-script-os": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", + "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", + "dev": true, + "bin": { + "run-os": "index.js", + "run-script-os": "index.js" + } + }, "node_modules/runtime-required": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/runtime-required/-/runtime-required-1.1.0.tgz", @@ -16200,31 +15639,10 @@ "node_modules/semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, "optional": true }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/semver/node_modules/lru-cache": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz", @@ -16483,6 +15901,27 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/sirv": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", @@ -16548,9 +15987,9 @@ } }, "node_modules/socks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", - "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", "dev": true, "dependencies": { "ip": "^2.0.0", @@ -17259,15 +16698,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -17470,15 +16900,17 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz", + "integrity": "sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==", "dev": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/tsconfig-paths-webpack-plugin": { @@ -17508,41 +16940,6 @@ "node": ">=10.13.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.1.tgz", - "integrity": "sha512-VgPrtLKpRgEAJsMj5Q/I/mXouC6A/7eJ/X4Nuk6o0cRPwBtznYxTCU4FodbexbzH9somBPEXYi0ZkUViUpJ21Q==", - "dev": true, - "dependencies": { - "json5": "^2.2.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -17579,16 +16976,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17643,15 +17030,6 @@ "rxjs": "*" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "4.7.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", @@ -17721,33 +17099,27 @@ } }, "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "dependencies": { - "unique-slug": "^2.0.0" + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/universalify": { @@ -17829,52 +17201,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/update-notifier/node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -17911,18 +17237,6 @@ } } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", @@ -18095,7 +17409,7 @@ "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "dependencies": { "defaults": "^1.0.3" @@ -18198,6 +17512,27 @@ "node": ">= 10" } }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webpack-cli": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", @@ -18447,27 +17782,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", @@ -18600,18 +17914,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -18650,29 +17952,16 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -18683,15 +17972,6 @@ } } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -20205,17 +19485,16 @@ "dev": true }, "@electron/get": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.14.1.tgz", - "integrity": "sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", + "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", "dev": true, "requires": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1", - "got": "^9.6.0", + "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" @@ -20300,51 +19579,6 @@ "cross-spawn": "^7.0.1" } }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -20356,40 +19590,6 @@ "universalify": "^2.0.0" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -20400,48 +19600,6 @@ "universalify": "^2.0.0" } }, - "keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -21142,9 +20300,9 @@ "dev": true }, "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true }, "@sinonjs/commons": { @@ -21314,49 +20472,43 @@ } }, "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "dev": true, "requires": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.0" } }, "@teamsupercell/typings-for-css-modules-loader": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.1.tgz", - "integrity": "sha512-8Dz/2awNbkrFHf3IpF8YGUPniXAZW/z7OTiosO+xucIU1+jVg/cT4uyGZ7z9cmAsnExsxq4igazxwgGBXVpUgA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.2.tgz", + "integrity": "sha512-3sqH2B4itcm5XgV1IHENt4NOaW7bOC1CwJr63vrdKWWyKVxNxtBM+ABVhJZYFCCVAwNy7ulA64z6HyQqw96m4A==", "dev": true, "requires": { "camelcase": "^5.3.1", - "loader-utils": "1.2.3", + "loader-utils": "^1.4.2", "prettier": "*", "schema-utils": "^2.0.1" }, "dependencies": { - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" } }, "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "dev": true, "requires": { "big.js": "^5.2.2", - "emojis-list": "^2.0.0", + "emojis-list": "^3.0.0", "json5": "^1.0.1" } }, @@ -21750,7 +20902,7 @@ "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "@types/keyv": { @@ -21769,9 +20921,9 @@ "dev": true }, "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, "optional": true }, @@ -21966,9 +21118,9 @@ } }, "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", "dev": true, "requires": { "@types/node": "*" @@ -22516,15 +21668,6 @@ "dev": true, "requires": {} }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -22580,9 +21723,9 @@ "dev": true }, "app-builder-lib": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.3.3.tgz", - "integrity": "sha512-m0+M53+HYMzqKxwNQZT143K7WwXEGUy9LY31l8dJphXx2P/FQod615mVbxHyqbDCG4J5bHdWm21qZ0e2DVY6CQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz", + "integrity": "sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==", "dev": true, "requires": { "@develar/schema-utils": "~2.6.5", @@ -22591,13 +21734,13 @@ "7zip-bin": "~5.1.1", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chromium-pickle-js": "^0.2.0", "debug": "^4.3.4", "ejs": "^3.1.7", "electron-osx-sign": "^0.6.0", - "electron-publish": "23.3.3", + "electron-publish": "23.6.0", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", @@ -22635,9 +21778,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23097,30 +22240,6 @@ "dev": true, "optional": true }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -23234,9 +22353,9 @@ "dev": true }, "builder-util": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.3.3.tgz", - "integrity": "sha512-MJZlUiq2PY5hjYv9+XNaoYdsITqvLgRDoHSFg/4nzpInbNxNjLQOolL04Zsyp+hgfcbFvMC4h0KkR1CMPHLWbA==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz", + "integrity": "sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==", "dev": true, "requires": { "@types/debug": "^4.1.6", @@ -23244,7 +22363,7 @@ "7zip-bin": "~5.1.1", "app-builder-bin": "4.0.0", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "9.0.3", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "cross-spawn": "^7.0.3", "debug": "^4.3.4", @@ -23288,10 +22407,9 @@ } }, "builder-util-runtime": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.0.3.tgz", - "integrity": "sha512-SfG2wnyjpUbbdtpnqDpWwklujofC6GarGpvdWrEkg9p5AD/xJmTF2buTNaqs3qtsNBEVQDDjZz9xc2GGpVyMfA==", - "dev": true, + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", + "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", "requires": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -23304,9 +22422,9 @@ "dev": true }, "cacache": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", - "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, "requires": { "@npmcli/fs": "^2.1.0", @@ -23326,7 +22444,7 @@ "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", - "unique-filename": "^1.1.1" + "unique-filename": "^2.0.0" }, "dependencies": { "brace-expansion": { @@ -23339,9 +22457,9 @@ } }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -23352,15 +22470,15 @@ } }, "lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", "dev": true }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -23381,35 +22499,18 @@ "dev": true }, "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", + "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" } }, "call-bind": { @@ -23558,12 +22659,6 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -23574,9 +22669,9 @@ } }, "cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", "dev": true }, "cli-truncate": { @@ -23603,7 +22698,7 @@ "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true }, "clone-deep": { @@ -23618,9 +22713,9 @@ } }, "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "requires": { "mimic-response": "^1.0.0" @@ -23779,31 +22874,6 @@ } } }, - "config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "optional": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, "confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -23951,12 +23021,6 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "css-declaration-sorter": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", @@ -24233,12 +23297,20 @@ "dev": true }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } } }, "dedent": { @@ -24247,12 +23319,6 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -24275,18 +23341,18 @@ } }, "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "requires": { "clone": "^1.0.2" } }, "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true }, "define-lazy-prop": { @@ -24428,14 +23494,14 @@ } }, "dmg-builder": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.3.3.tgz", - "integrity": "sha512-ECwAjt+ZWyOvddrkDx1xRD6IVUCZb5SV6vSMHZd+Va3G2sUXHrnglR1cGDKRF4oYRQm8SYVrpLZKbi8npyDcAQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz", + "integrity": "sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==", "dev": true, "requires": { - "app-builder-lib": "23.3.3", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "app-builder-lib": "23.6.0", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "dmg-license": "^1.0.11", "fs-extra": "^10.0.0", "iconv-lite": "^0.6.2", @@ -24583,15 +23649,6 @@ "tslib": "^2.0.3" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, "dotenv": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", @@ -24610,12 +23667,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -24638,12 +23689,12 @@ } }, "electron": { - "version": "20.0.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-20.0.2.tgz", - "integrity": "sha512-Op4nxSyXH0tXjhvWC+WDn9EI0gep5etPccainxu1A4wes+ZFQBMCBXxibotanJfG+WNW4RaOv88NArwHIsSmPw==", + "version": "22.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-22.0.3.tgz", + "integrity": "sha512-eETrJTINTzlXgQrnJSrKiF2Xdt5EHpxZ6Kk+WUjFCE0zUztdVm+hrngUecqhj8TPFlYScTANzPwRwUIjOChl+g==", "dev": true, "requires": { - "@electron/get": "^1.14.1", + "@electron/get": "^2.0.0", "@types/node": "^16.11.26", "extract-zip": "^2.0.1" }, @@ -24657,23 +23708,23 @@ } }, "electron-builder": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.3.3.tgz", - "integrity": "sha512-mFYYdhoFPKevP6y5uaaF3dusmB2OtQ/HnwwpyOePeU7QDS0SEIAUokQsHUanAiJAZcBqtY7iyLBgX18QybdFFw==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz", + "integrity": "sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==", "dev": true, "requires": { "@types/yargs": "^17.0.1", - "app-builder-lib": "23.3.3", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "app-builder-lib": "23.6.0", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", - "dmg-builder": "23.3.3", + "dmg-builder": "23.6.0", "fs-extra": "^10.0.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", "read-config-file": "6.2.0", - "update-notifier": "^5.1.0", - "yargs": "^17.0.1" + "simple-update-notifier": "^1.0.7", + "yargs": "^17.5.1" }, "dependencies": { "fs-extra": { @@ -24793,14 +23844,14 @@ } }, "electron-publish": { - "version": "23.3.3", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.3.3.tgz", - "integrity": "sha512-1dX17eE5xVXedTxjC+gjsP74oC0+sIHgqysp0ryTlF9+yfQUyXjBk6kcK+zhtBA2SsHMSglDtM+JPxDD/WpPTQ==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz", + "integrity": "sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==", "dev": true, "requires": { "@types/fs-extra": "^9.0.11", - "builder-util": "23.3.3", - "builder-util-runtime": "9.0.3", + "builder-util": "23.6.0", + "builder-util-runtime": "9.1.1", "chalk": "^4.1.1", "fs-extra": "^10.0.0", "lazy-val": "^1.0.5", @@ -24858,15 +23909,6 @@ "typed-emitter": "^2.1.0" }, "dependencies": { - "builder-util-runtime": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", - "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", - "requires": { - "debug": "^4.3.4", - "sax": "^1.2.4" - } - }, "fs-extra": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", @@ -25100,12 +24142,6 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -25710,11 +24746,38 @@ "esutils": "^2.0.2" } }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } } } }, @@ -26065,17 +25128,6 @@ "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - } } }, "extsprintf": { @@ -26213,9 +25265,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -26440,9 +25492,9 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -26508,36 +25560,6 @@ "serialize-error": "^7.0.1" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - } - } - }, - "global-tunnel-ng": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", - "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", - "dev": true, - "optional": true, - "requires": { - "encodeurl": "^1.0.2", - "lodash": "^4.17.10", - "npm-conf": "^1.1.3", - "tunnel": "^0.0.6" - } - }, "globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -26548,9 +25570,9 @@ } }, "globalthis": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", - "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "optional": true, "requires": { @@ -26584,22 +25606,22 @@ "dev": true }, "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, "graceful-fs": { @@ -26691,12 +25713,6 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -26987,12 +26003,6 @@ } } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -27037,12 +26047,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -27172,16 +26176,6 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -27200,12 +26194,6 @@ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -27221,18 +26209,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -27297,12 +26273,6 @@ "has-symbols": "^1.0.2" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -27327,12 +26297,6 @@ "is-docker": "^2.0.0" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -28234,15 +27198,6 @@ "whatwg-url": "^10.0.0", "ws": "^8.2.3", "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "ws": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", - "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", - "dev": true, - "requires": {} - } } }, "jsesc": { @@ -28252,9 +27207,9 @@ "dev": true }, "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, "json-parse-even-better-errors": { @@ -28278,20 +27233,20 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "optional": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "requires": { "graceful-fs": "^4.1.6" } @@ -28329,12 +27284,12 @@ "integrity": "sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==" }, "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", "dev": true, "requires": { - "json-buffer": "3.0.0" + "json-buffer": "3.0.1" } }, "kind-of": { @@ -28370,15 +27325,6 @@ "language-subtag-registry": "~0.3.2" } }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", @@ -28614,9 +27560,9 @@ "dev": true }, "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -28743,9 +27689,9 @@ } }, "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true }, "lru-cache": { @@ -28841,9 +27787,9 @@ }, "dependencies": { "lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", "dev": true } } @@ -28888,6 +27834,11 @@ "fs-monkey": "1.0.3" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "memory-fs": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", @@ -29056,9 +28007,9 @@ } }, "minipass-fetch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.0.tgz", - "integrity": "sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, "requires": { "encoding": "^0.1.13", @@ -29168,9 +28119,9 @@ } }, "node-abi": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz", - "integrity": "sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz", + "integrity": "sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==", "dev": true, "requires": { "semver": "^7.3.5" @@ -29199,16 +28150,16 @@ "dev": true }, "node-gyp": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.1.0.tgz", - "integrity": "sha512-HkmN0ZpQJU7FLbJauJTHkHlSVAXlNGDAzH/VYFZGDOnFyn/Na3GlNJfkudmufOdS6/jNFhy88ObzL7ERz9es1g==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", "dev": true, "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.0.3", - "nopt": "^5.0.0", + "nopt": "^6.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", @@ -29217,9 +28168,9 @@ } }, "node-gyp-build": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", "dev": true }, "node-int64": { @@ -29235,12 +28186,12 @@ "dev": true }, "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "^1.0.0" } }, "normalize-path": { @@ -29250,22 +28201,11 @@ "dev": true }, "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true }, - "npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "dev": true, - "optional": true, - "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" - } - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -29463,9 +28403,9 @@ } }, "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true }, "p-limit": { @@ -29519,26 +28459,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -29658,13 +28578,6 @@ "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -29968,14 +28881,6 @@ "requires": { "normalize-url": "^6.0.1", "postcss-value-parser": "^4.2.0" - }, - "dependencies": { - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - } } }, "postcss-normalize-whitespace": { @@ -30057,12 +28962,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", @@ -30164,13 +29063,6 @@ } } }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true, - "optional": true - }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -30211,15 +29103,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", @@ -30282,26 +29165,6 @@ } } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -30493,24 +29356,6 @@ "unicode-match-property-value-ecmascript": "^2.0.0" } }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -30624,12 +29469,12 @@ "dev": true }, "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dev": true, "requires": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "^2.0.0" } }, "restore-cursor": { @@ -30693,6 +29538,12 @@ "queue-microtask": "^1.2.2" } }, + "run-script-os": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", + "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", + "dev": true + }, "runtime-required": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/runtime-required/-/runtime-required-1.1.0.tgz", @@ -30816,27 +29667,10 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, "optional": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -31050,6 +29884,23 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "sirv": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", @@ -31102,9 +29953,9 @@ } }, "socks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", - "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", "dev": true, "requires": { "ip": "^2.0.0", @@ -31648,12 +30499,6 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -31780,26 +30625,16 @@ } }, "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz", + "integrity": "sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -31828,23 +30663,6 @@ "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "tsconfig-paths": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.1.tgz", - "integrity": "sha512-VgPrtLKpRgEAJsMj5Q/I/mXouC6A/7eJ/X4Nuk6o0cRPwBtznYxTCU4FodbexbzH9somBPEXYi0ZkUViUpJ21Q==", - "dev": true, - "requires": { - "json5": "^2.2.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } } } }, @@ -31871,13 +30689,6 @@ } } }, - "tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "optional": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -31917,15 +30728,6 @@ "rxjs": "*" } }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript": { "version": "4.7.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", @@ -31973,32 +30775,23 @@ "dev": true }, "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "requires": { - "unique-slug": "^2.0.0" + "unique-slug": "^3.0.0" } }, "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, "requires": { "imurmurhash": "^0.1.4" } }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -32052,45 +30845,6 @@ "picocolors": "^1.0.0" } }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -32111,15 +30865,6 @@ "schema-utils": "^3.0.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", @@ -32269,7 +31014,7 @@ "wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "requires": { "defaults": "^1.0.3" @@ -32353,6 +31098,13 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} } } }, @@ -32519,13 +31271,6 @@ "ajv-formats": "^2.1.1", "ajv-keywords": "^5.0.0" } - }, - "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} } } }, @@ -32618,15 +31363,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -32656,31 +31392,12 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", "requires": {} }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/package.json b/package.json index 682fa97793..ff93b153fc 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,15 @@ { "name": "Ira J Hopkinson", "url": "https://github.com/irahopkinson" + }, + { + "name": "Tim Steenwyk", + "url": "https://github.com/FoolRunning" + }, + { + "name": "TJ Couch", + "email": "tj_couch@sil.org", + "url": "https://github.com/tjcouch-sil" } ], "main": "./src/main/main.ts", @@ -38,10 +47,11 @@ "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never", + "package:debug": "cross-env DEBUG_PROD=true npm run package", "prepare": "husky install", "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app", "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer", - "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .", + "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only -r tsconfig-paths/register .", "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", "test": "jest" @@ -61,59 +71,16 @@ ] }, "browserslist": [], - "prettier": { - "singleQuote": true, - "overrides": [ - { - "files": [ - ".prettierrc", - ".eslintrc" - ], - "options": { - "parser": "json" - } - } - ] - }, - "jest": { - "moduleDirectories": [ - "node_modules", - "release/app/node_modules", - "src" - ], - "moduleFileExtensions": [ - "js", - "jsx", - "ts", - "tsx", - "json" - ], - "moduleNameMapper": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", - "\\.(css|less|sass|scss)$": "identity-obj-proxy" - }, - "setupFiles": [ - "./.erb/scripts/check-build-exists.ts" - ], - "testEnvironment": "jsdom", - "testEnvironmentOptions": { - "url": "http://localhost/" - }, - "testPathIgnorePatterns": [ - "release/app/dist" - ], - "transform": { - "\\.(ts|tsx|js|jsx)$": "ts-jest" - } - }, "dependencies": { "electron-debug": "^3.2.0", "electron-log": "^4.4.8", "electron-updater": "^5.2.3", "electron-window-state": "^5.0.3", + "memoize-one": "^6.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.4.0" + "react-router-dom": "^6.4.0", + "ws": "^8.12.0" }, "devDependencies": { "@electron/notarize": "^1.2.3", @@ -130,6 +97,7 @@ "@types/react-test-renderer": "^18.0.0", "@types/terser-webpack-plugin": "^5.0.4", "@types/webpack-bundle-analyzer": "^4.4.2", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/parser": "^5.33.1", "browserslist-config-erb": "^0.0.3", @@ -140,8 +108,8 @@ "css-loader": "^6.7.1", "css-minimizer-webpack-plugin": "^4.0.0", "detect-port": "^1.3.0", - "electron": "^20.0.2", - "electron-builder": "^23.3.3", + "electron": "^22.0.3", + "electron-builder": "^23.6.0", "electron-devtools-installer": "^3.2.0", "electronmon": "^2.0.2", "eslint": "^8.22.0", @@ -168,6 +136,7 @@ "react-refresh": "^0.14.0", "react-test-renderer": "^18.2.0", "rimraf": "^3.0.2", + "run-script-os": "^1.1.6", "sass": "^1.54.4", "sass-loader": "^13.0.2", "style-loader": "^3.3.1", @@ -175,6 +144,7 @@ "ts-jest": "^28.0.8", "ts-loader": "^9.3.1", "ts-node": "^10.9.1", + "tsconfig-paths": "^4.1.2", "tsconfig-paths-webpack-plugin": "^4.0.0", "typescript": "^4.7.4", "url-loader": "^4.1.1", @@ -515,7 +485,8 @@ "electronmon": { "patterns": [ "!**/**", - "src/main/**" + "src/main/**", + "src/shared/**" ], "logLevel": "quiet" } diff --git a/release/app/package.json b/release/app/package.json index 1fac6fe00b..7ebb751ee6 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -4,8 +4,8 @@ "description": "Extensible Bible translation software", "license": "MIT", "author": { - "name": "SIL International", - "url": "https://software.sil.org/" + "name": "Paranext", + "url": "https://github.com/paranext/" }, "main": "./dist/main/main.js", "scripts": { diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index 6a1de2a639..af73bc9481 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -2,8 +2,19 @@ import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; import App from '../renderer/App'; +jest.mock('@shared/services/NetworkService', () => ({ + createRequestFunction: + (requestType: string) => + async (...args: unknown[]) => + `Mocked ${requestType} request with args ${args.join(', ')}`, +})); +jest.mock('@renderer/hooks/usePromise', () => ({ + __esModule: true, + default: /** usePromise Mock */ () => ['mock', false], +})); + describe('App', () => { - it('should render', () => { + it('should render', async () => { expect(render()).toBeTruthy(); }); }); diff --git a/src/main/main.ts b/src/main/main.ts index ae7bb016e0..4cf5c4764b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -12,10 +12,15 @@ import path from 'path'; import { app, BrowserWindow, shell, ipcMain } from 'electron'; import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; +import * as NetworkService from '@shared/services/NetworkService'; +import papi from '@shared/services/papi'; +import { CommandHandler } from '@shared/util/PapiUtil'; import windowStateKeeper from 'electron-window-state'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; +// #region ELECTRON SETUP + class AppUpdater { constructor() { log.transports.file.level = 'info'; @@ -24,14 +29,10 @@ class AppUpdater { } } +// Keep a global reference of the window object. If you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. let mainWindow: BrowserWindow | null = null; -ipcMain.on('ipc-example', async (event, arg) => { - const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; - console.log(msgTemplate(arg)); - event.reply('ipc-example', msgTemplate('pong')); -}); - if (process.env.NODE_ENV === 'production') { const sourceMapSupport = require('source-map-support'); sourceMapSupport.install(); @@ -44,6 +45,7 @@ if (isDebug) { require('electron-debug')(); } +/** Install extensions into the Chromium renderer process */ const installExtensions = async () => { const installer = require('electron-devtools-installer'); const forceDownload = !!process.env.UPGRADE_EXTENSIONS; @@ -52,24 +54,26 @@ const installExtensions = async () => { return installer .default( extensions.map((name) => installer[name]), - forceDownload + forceDownload, ) .catch(console.log); }; +/** The path to the app package directory */ +const RESOURCES_PATH = app.isPackaged + ? process.resourcesPath + : path.join(__dirname, '../../'); + +const getAssetPath = (...paths: string[]): string => { + return path.join(RESOURCES_PATH, 'assets', ...paths); +}; + +/** Sets up the electron BrowserWindow renderer process */ const createWindow = async () => { if (isDebug) { await installExtensions(); } - const RESOURCES_PATH = app.isPackaged - ? path.join(process.resourcesPath, 'assets') - : path.join(__dirname, '../../assets'); - - const getAssetPath = (...paths: string[]): string => { - return path.join(RESOURCES_PATH, ...paths); - }; - // Load the previous state with fallback to defaults const mainWindowState = windowStateKeeper({ defaultWidth: 1024, @@ -126,10 +130,6 @@ const createWindow = async () => { new AppUpdater(); }; -/** - * Add event listeners... - */ - app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed @@ -138,9 +138,31 @@ app.on('window-all-closed', () => { } }); +// #endregion + +// #region IPC HANDLING SETUP + +/** Map from ipc channel to handler function */ +const ipcHandlers: { + [ipcHandle: string]: ( + event: Electron.IpcMainInvokeEvent, + // We don't know the exact parameter types since ipc handlers can be anything + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: any[] + ) => Promise | unknown; +} = { + 'electronAPI.env.getVar': (_event, name: string) => process.env[name], + 'electronAPI.env.test': (_event) => 'From main.ts: test', +}; + app .whenReady() .then(() => { + // Set up ipc handlers + Object.keys(ipcHandlers).forEach((ipcHandle) => + ipcMain.handle(ipcHandle, ipcHandlers[ipcHandle]), + ); + createWindow(); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the @@ -149,3 +171,39 @@ app }); }) .catch(console.log); + +// #endregion + +// #region Services setup + +const commandHandlers: { [commandName: string]: CommandHandler } = { + echo: async (message: string) => { + /* const start = performance.now(); */ + /* const result = */ await papi.commands.sendCommand('addThree', 1, 4, 9); + /* console.log( + `addThree(...) = ${result} took ${performance.now() - start} ms`, + ); */ + return message; + }, + throwError: async (message: string) => { + throw new Error(`Test Error thrown in throwError command: ${message}`); + }, +}; + +NetworkService.initialize() + .then(() => { + // Set up test handlers + Object.entries(ipcHandlers).forEach(([ipcHandle, handler]) => { + NetworkService.registerRequestHandler( + ipcHandle, + async (...args: unknown[]) => + handler({} as Electron.IpcMainInvokeEvent, ...args), + ); + }); + Object.entries(commandHandlers).forEach(([commandName, handler]) => { + papi.commands.registerCommand(commandName, handler); + }); + }) + .catch((e) => console.error(e)); + +// #endregion diff --git a/src/main/menu.ts b/src/main/menu.ts index c8c57558b2..ba0fb77096 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -164,7 +164,7 @@ export default class MenuBuilder { label: 'Documentation', click() { shell.openExternal( - 'https://github.com/electron/electron/tree/main/docs#readme' + 'https://github.com/electron/electron/tree/main/docs#readme', ); }, }, @@ -228,7 +228,7 @@ export default class MenuBuilder { accelerator: 'F11', click: () => { this.mainWindow.setFullScreen( - !this.mainWindow.isFullScreen() + !this.mainWindow.isFullScreen(), ); }, }, @@ -246,7 +246,7 @@ export default class MenuBuilder { accelerator: 'F11', click: () => { this.mainWindow.setFullScreen( - !this.mainWindow.isFullScreen() + !this.mainWindow.isFullScreen(), ); }, }, @@ -265,7 +265,7 @@ export default class MenuBuilder { label: 'Documentation', click() { shell.openExternal( - 'https://github.com/electron/electron/tree/main/docs#readme' + 'https://github.com/electron/electron/tree/main/docs#readme', ); }, }, diff --git a/src/main/preload.ts b/src/main/preload.ts index 587963c6d1..a083a3e9d6 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,27 +1,19 @@ -import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; -export type Channels = 'ipc-example'; - -const electronHandler = { - ipcRenderer: { - sendMessage(channel: Channels, args: unknown[]) { - ipcRenderer.send(channel, args); - }, - on(channel: Channels, func: (...args: unknown[]) => void) { - const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => - func(...args); - ipcRenderer.on(channel, subscription); - - return () => { - ipcRenderer.removeListener(channel, subscription); - }; - }, - once(channel: Channels, func: (...args: unknown[]) => void) { - ipcRenderer.once(channel, (_event, ...args) => func(...args)); - }, +const electronAPIHandler = { + env: { + /** + * Get an environment variable's value + * TODO: remove this as it is only for debugging purposes + * @param name name of environment variable to get + * @returns string of environment variable contents + */ + getVar: (name: string): Promise => + ipcRenderer.invoke('electronAPI.env.getVar', name), + test: () => ipcRenderer.invoke('electronAPI.env.test'), }, }; -contextBridge.exposeInMainWorld('electron', electronHandler); +contextBridge.exposeInMainWorld('electronAPI', electronAPIHandler); -export type ElectronHandler = typeof electronHandler; +export type ElectronAPIHandler = typeof electronAPIHandler; diff --git a/src/main/services/ServerNetworkConnector.ts b/src/main/services/ServerNetworkConnector.ts new file mode 100644 index 0000000000..97736493da --- /dev/null +++ b/src/main/services/ServerNetworkConnector.ts @@ -0,0 +1,484 @@ +import { CloseEvent, MessageEvent, WebSocket, WebSocketServer } from 'ws'; +import { + CLIENT_ID_SERVER, + ConnectionStatus, + InternalRequest, + InternalRequestHandler, + InternalResponse, + NetworkConnectorInfo, +} from '@shared/data/InternalConnectionTypes'; +import INetworkConnector from '@shared/services/INetworkConnector'; +import { Unsubscriber } from '@shared/util/PapiUtil'; +import { + ClientConnect, + Message, + MessageType, + WebSocketRequest, + WebSocketResponse, + WEBSOCKET_PORT, +} from '@shared/data/NetworkConnectorTypes'; + +// #region local variables + +// TODO: implement request timeout logic +/** Holds promises for a request */ +type LiveRequest = { + requestId: number; + resolve: ( + value: InternalResponse | PromiseLike>, + ) => void; + reject: (reason?: unknown) => void; +}; + +/** A WebSocket client and information about its connection */ +type WebSocketClient = { + webSocket: WebSocket; + connectorInfo: NetworkConnectorInfo; + /** Whether the client has responded to initClient and told us it is ready to receive messages */ + connected: boolean; +}; + +// #endregion + +/** + * Handles the endpoint and connections from the server to the clients + */ +export default class ServerNetworkConnector implements INetworkConnector { + // #region INetworkConnector members + + connectorInfo: NetworkConnectorInfo = { clientId: CLIENT_ID_SERVER }; + + connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected; + + // #endregion + + // #region private members + + /** The webSocket connected to the server */ + private webSocketServer?: WebSocketServer; + + /** The next client id to use for a new connection. Starts at 1 because the server is 0 */ + private nextClientId = 1; + + /** The webSocket clients that are connected and information about them */ + private clientSockets = new Map(); + + /** All message subscriptions - arrays of functions that run each time a message with a specific message type comes in */ + private messageSubscriptions = new Map< + MessageType, + ((eventData: Message, clientId: number) => void)[] + >(); + + /** Promise that resolves when finished starting the server or rejects if disconnected before the server finishes */ + private connectPromise?: Promise; + + /** Function that removes this clientConnect handler from connections */ + private unsubscribeHandleClientConnectMessage?: () => boolean; + + /** Function that removes this response handler from connections */ + private unsubscribeHandleResponseMessage?: () => boolean; + + /** Function that removes this handleRequest from connections */ + private unsubscribeHandleRequestMessage?: () => boolean; + + /** + * Function to call when we receive a request that is registered on this connector. + * Handles requests from connections and returns a response to send back + */ + private localRequestHandler?: InternalRequestHandler; + + /** + * Function to call when we are sending a request. + * Returns a clientId to which to send the request based on the requestType + */ + private requestRouter?: (requestType: string) => number; + + /** All requests that are waiting for a response */ + // Disabled no-explicit-any because assigning a request with generic type to LiveRequest gave error + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private requests = new Map>(); + + // #endregion + + // #region INetworkConnector methods + + connect = async ( + localRequestHandler: InternalRequestHandler, + requestRouter: (requestType: string) => number, + ) => { + // NOTE: This does not protect against sending two different request handlers. See ConnectionService for that + // We don't need to run this more than once + if (this.connectPromise) return this.connectPromise; + + this.connectionStatus = ConnectionStatus.Connecting; + this.localRequestHandler = localRequestHandler; + this.requestRouter = requestRouter; + + // Set up subscriptions that the service needs to work + // Mark the connection fully connected and notify that a client was connected + this.unsubscribeHandleClientConnectMessage = this.subscribe( + MessageType.ClientConnect, + (clientConnect: ClientConnect, clientId) => { + // Verify that the client has the correct clientId. Otherwise nothing will work properly + if (clientId !== clientConnect.senderId) + // TODO: tell the client that they messed up, not throw an exception on the server + throw new Error( + `WebSocket with clientId ${clientId} tried to finalize connection with incorrect senderId ${clientConnect.senderId}`, + ); + // Client finished connecting! + this.getClientSocket(clientId).connected = true; + // TODO: Send an event that the client is fully connected + }, + ); + + // Listen for responses from the clients and resolve the request promise + this.unsubscribeHandleResponseMessage = this.subscribe( + MessageType.Response, + this.handleResponseMessage, + ); + + // Listen for requests from the clients and run the request handler + this.unsubscribeHandleRequestMessage = this.subscribe( + MessageType.Request, + this.handleRequestMessage, + ); + + // Start the webSocket server + this.webSocketServer = new WebSocketServer({ port: WEBSOCKET_PORT }); + this.webSocketServer.on('connection', this.onClientConnect); + this.webSocketServer.on('close', this.disconnect); + + // Finished setting up server synchronously with this implementation. + this.connectionStatus = ConnectionStatus.Connected; + this.connectPromise = Promise.resolve(this.connectorInfo); + + return this.connectPromise; + }; + + // Don't need self here because there's nothing to do. This is just implementing the interface method as a placeholder for now + // eslint-disable-next-line class-methods-use-this + notifyClientConnected = async () => { + // Don't think we need to do anything to tell the client that the server is ready + return Promise.resolve(); + }; + + disconnect = () => { + this.connectionStatus = ConnectionStatus.Disconnected; + this.localRequestHandler = undefined; + this.connectPromise = undefined; + + // Disconnect all clients - this should clear clientSockets on its own + [...this.clientSockets.values()].forEach((clientSocket) => + this.disconnectClient(clientSocket.webSocket), + ); + + if (this.webSocketServer) { + this.webSocketServer.off('connection', this.onClientConnect); + this.webSocketServer.off('close', this.disconnect); + this.webSocketServer.close(); + this.webSocketServer = undefined; + } + + if (this.unsubscribeHandleClientConnectMessage) + this.unsubscribeHandleClientConnectMessage(); + if (this.unsubscribeHandleResponseMessage) + this.unsubscribeHandleResponseMessage(); + if (this.unsubscribeHandleRequestMessage) + this.unsubscribeHandleRequestMessage(); + }; + + request = async ( + requestType: string, + request: InternalRequest, + ) => { + // Set up a promise we can resolve later + let liveRequest: LiveRequest | undefined; + const requestPromise = new Promise>( + (resolve, reject) => { + liveRequest = { + requestId: request.requestId, + resolve, + reject, + }; + }, + ); + + if (!liveRequest) + throw new Error( + `Live request was not created for requestId ${request.requestId}`, + ); + + // Save the live request to resolve when we get the response + this.requests.set(request.requestId, liveRequest); + + // Send the request corresponding to the live request promise + this.handleRequestMessage( + { + type: MessageType.Request, + requestType, + ...request, + }, + this.connectorInfo.clientId, + ); + + return requestPromise; + }; + + // #endregion + + // #region private methods + + /** Get the client socket for a certain clientId. Throws if not found */ + private getClientSocket = (clientId: number): WebSocketClient => { + if (!this.webSocketServer) + throw new Error('Trying to get client socket when not connected!'); + + const clientSocket = this.clientSockets.get(clientId); + + if (!clientSocket) + throw new Error( + `Trying to get client socket ${clientId} but it does not exist!`, + ); + + return clientSocket; + }; + + /** Get the clientId for a certain webSocket. Throws if not found */ + private getClientIdFromSocket = (webSocket: WebSocket): number => { + // Using for...of on iterator here because it is significantly faster (not converting to array first) and cleaner this way in this case + // eslint-disable-next-line no-restricted-syntax + for (const [clientId, clientSocket] of this.clientSockets.entries()) + if (clientSocket.webSocket === webSocket) return clientId; + throw new Error('Could not find clientId for webSocket'); + }; + + /** + * Send a message to a client via webSocket. Throws if not connected + * @param message message to send + * @param recipientId the client to which to send the message. TODO: determine if we can intuit this instead + */ + // Add if needed: @param unsafe whether to get the client even if we aren't connected. WARNING: SETTING THIS FLAG MEANS NOT CHECKING FOR INITIALIZATION. DO NOT USE OUTSIDE OF INITIALIZATION. There may be no clientId + private sendMessage = (message: Message, recipientId: number): void => { + // TODO: add message queueing + if ( + this.connectionStatus !== ConnectionStatus.Connected || + !this.webSocketServer + ) + throw new Error( + `Trying to send message when not connected! Message ${message}`, + ); + + if (this.connectorInfo.clientId === recipientId) { + // This message is from us and for us. Handle the message as if we just received it + this.onMessage( + { + data: message as unknown as string, + } as MessageEvent, + true, + ); + } else { + // This message is for someone else. Send the message + this.getClientSocket(recipientId).webSocket.send(JSON.stringify(message)); + } + }; + + /** + * Receives and appropriately publishes webSocket messages + * @param event webSocket message information + * @param fromSelf whether this message is from this connector instead of from someone else + */ + private onMessage = (event: MessageEvent, fromSelf = false) => { + const data = fromSelf + ? (event.data as unknown as Message) + : (JSON.parse(event.data as string) as Message); + + // Make sure the client isn't impersonating another client + // TODO: consider speeding up validation by passing in webSocket client id? + const clientId = fromSelf + ? this.connectorInfo.clientId + : this.getClientIdFromSocket(event.target); + if (data.senderId !== clientId) + throw new Error( + `Received message from webSocket ${clientId} but did not match indicated message sender id ${data.senderId}`, + ); + + const callbacks = this.messageSubscriptions.get(data.type); + callbacks?.forEach((callback) => callback(data, clientId)); + }; + + /** + * Unsubscribes a function from running on webSocket messages + * @param messageType the type of message from which to unsubscribe the function + * @param callback function to unsubscribe from being run on webSocket messages. + * @returns true if successfully unsubscribed + * Likely will never need to be exported from this file. Just use subscribe, which returns a matching unsubscriber function that runs this. + */ + private unsubscribe = ( + messageType: MessageType, + callback: (eventData: Message, clientId: number) => void, + ): boolean => { + const callbacks = this.messageSubscriptions.get(messageType); + + if (!callbacks) return false; // Did not find any callbacks for the message type + + const callbackIndex = callbacks.indexOf(callback); + if (callbackIndex < 0) return false; // Did not find this callback for the message type + + // Remove the callback + callbacks.splice(callbackIndex, 1); + + // Remove the map entry if there are no more callbacks + if (callbacks.length === 0) this.messageSubscriptions.delete(messageType); + + // Indicate successfully removed the callback + return true; + }; + + /** + * Subscribes a function to run on webSocket messages of a particular type + * @param messageType the type of message on which to subscribe the function + * @param callback function to run with the contents of the webSocket message + * @returns unsubscriber function to run to stop calling the passed-in function on webSocket messages + */ + private subscribe = ( + messageType: MessageType, + // Any is here because I dunno how to narrow Message type to a specific message type in parameters of a function + // TODO: investigate this further another time + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (eventData: Message | any, clientId: number) => void, + ): Unsubscriber => { + let callbacks = this.messageSubscriptions.get(messageType); + if (!callbacks) { + callbacks = []; + this.messageSubscriptions.set(messageType, callbacks); + } + callbacks.push(callback); + + return () => this.unsubscribe(messageType, callback); + }; + + /** + * Registers an incoming webSocket connection and sends connection info with InitClient. + * Does not consider the client fully connected yet until they respond and tell us they connected with ClientConnect + */ + private onClientConnect = (webSocket: WebSocket) => { + const clientId = this.nextClientId; + this.nextClientId += 1; + + // TODO: probably do something better than just print the error + webSocket.addEventListener('error', console.error); + + webSocket.addEventListener('message', this.onMessage); + webSocket.addEventListener('close', this.onClientDisconnect); + + /** This clientSocket's connector info */ + const connectorInfo: NetworkConnectorInfo = { clientId }; + + // Add the client socket to the list + this.clientSockets.set(clientId, { + webSocket, + connectorInfo, + connected: false, + }); + + // Send initclient to let the client know its connectorInfo + this.sendMessage( + { + type: MessageType.InitClient, + senderId: this.connectorInfo.clientId, + connectorInfo, + }, + clientId, + ); + }; + + /** Handles when client connection disconnects. Unregisters and such */ + private onClientDisconnect = (event: CloseEvent) => { + this.disconnectClient(event.target); + }; + + /** Closes connection and unregisters a client webSocket when it has disconnected */ + private disconnectClient = (webSocket: WebSocket) => { + const clientId = this.getClientIdFromSocket(webSocket); + webSocket.removeEventListener('error', console.error); + webSocket.removeEventListener('message', this.onMessage); + webSocket.removeEventListener('close', this.onClientDisconnect); + webSocket.close(); + this.clientSockets.delete(clientId); + }; + + /** + * Function that handles webSocket messages of type Response. + * Resolves the request associated with the received response message or forwards to appropriate client + * @param response Response message to resolve + * @param responderId responding client + */ + private handleResponseMessage = ( + response: WebSocketResponse, + responderId: number, + ) => { + const { requesterId, requestId } = response; + if (this.connectorInfo.clientId === requesterId) { + // This response is for us. Resolve the associated request + const liveRequest = this.requests.get(requestId); + if (!liveRequest) + throw new Error( + `Received response from responder senderId ${responderId} for nonexistent requestId ${requestId}`, + ); + + // Remove the request from the requests because it is receiving a response + this.requests.delete(requestId); + + // Run the request's response function with the response + liveRequest.resolve(response); + } else { + // This response is for someone else. Forward the response on + this.sendMessage(response, requesterId); + } + }; + + /** + * Function that handles incoming webSocket messages and locally sent messages of type Request. + * Handles the request and sends a response if we have a handler or forwards to the appropriate client + * @param requestMessage request to handle + * @param requesterId who sent this message + */ + private handleRequestMessage = async ( + requestMessage: WebSocketRequest, + requesterId: number, + ) => { + if (!this.requestRouter) + throw new Error( + `Received a request but cannot route it without a requestRouter`, + ); + + // Figure out if we can handle this request or if we need to send it + const responderId = this.requestRouter(requestMessage.requestType); + if (this.connectorInfo.clientId === responderId) { + // This request is ours. Handle the request + if (!this.localRequestHandler) + throw Error('Handling request without a requestHandler!'); + + // Run the request handler for this request + const response = await this.localRequestHandler( + requestMessage.requestType, + requestMessage, + ); + + // Send the response to this request + this.sendMessage( + { + type: MessageType.Response, + requestType: requestMessage.requestType, + ...response, + }, + requesterId, + ); + } else { + // This request is for someone else. Forward the request on + this.sendMessage(requestMessage, responderId); + } + }; + + // #endregion +} diff --git a/src/renderer/App.css b/src/renderer/App.css index 9bb35dbce3..5fd7095dec 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -56,7 +56,12 @@ a:hover { .Hello { display: flex; + flex-direction: column; justify-content: center; align-items: center; margin: 20px 0; } + +.Hello > div { + text-align: center; +} diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index d081696861..6346ee2797 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,8 +1,94 @@ +import { useCallback, useState } from 'react'; import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; -import icon from '../../assets/icon.png'; +import * as NetworkService from '@shared/services/NetworkService'; +import icon from '@assets/icon.png'; import './App.css'; +import papi from '@shared/services/papi'; +import { getErrorMessage } from '@shared/util/Util'; +import usePromise from '@renderer/hooks/usePromise'; + +const getVar: (envVar: string) => Promise = + NetworkService.createRequestFunction('electronAPI.env.getVar'); + +const testBase: () => Promise = NetworkService.createRequestFunction( + 'electronAPI.env.test', +); + +const test = async () => { + /* const start = performance.now(); */ + const result = await testBase(); + /* console.log(`Test took ${performance.now() - start} ms`); */ + return result; +}; + +const echo = async (message: string) => + papi.commands.sendCommand<[string], string>('echo', message); + +const throwError = async (message: string) => + papi.commands.sendCommand<[string], string>('throwError', message); + +const executeMany = async (fn: () => Promise) => { + const numRequests = 10000; + const requests = new Array>(numRequests); + const requestTime = new Array(numRequests); + const start = performance.now(); + for (let i = 0; i < numRequests; i++) { + requestTime[i] = performance.now(); + requests[i] = fn() + .then((response) => { + requestTime[i] = performance.now() - requestTime[i]; + return response; + }) + .catch((err) => console.error(err)); + } + + try { + const responses = await Promise.all(requests); + const finish = performance.now(); + + const avgResponseTime = + requestTime.reduce((sum, time) => sum + time, 0) / numRequests; + const maxTime = requestTime.reduce((max, time) => Math.max(max, time), 0); + const minTime = requestTime.reduce( + (min, time) => Math.min(min, time), + Number.MAX_VALUE, + ); + console.log( + `Of ${numRequests} requests:\n\tAvg response time: ${avgResponseTime} ms\n\tMax response time: ${maxTime} ms\n\tMin response time: ${minTime}\n\tTotal time: ${ + finish - start + }\n\tResponse times:`, + requestTime, + ); + console.log(responses[responses.length - 1]); + } catch (e) { + console.error(e); + } +}; const Hello = () => { + const [promiseReturn, setPromiseReturn] = useState(''); + + const [NODE_ENV] = usePromise( + useCallback(() => getVar('NODE_ENV'), []), + 'retrieving', + ); + + const runPromise = useCallback( + async (asyncFn: () => Promise) => { + try { + const result = await asyncFn(); + console.log(result); + setPromiseReturn(JSON.stringify(result)); + return result; + } catch (e) { + console.error(e); + setPromiseReturn(`Error: ${getErrorMessage(e)}`); + return undefined; + } + }, + [setPromiseReturn], + ); + return (
@@ -11,6 +97,44 @@ const Hello = () => {

Paranext

+
+ + + +
+
+
NODE_ENV: {NODE_ENV}
+
{promiseReturn}
+
); }; diff --git a/src/renderer/hooks/usePromise.ts b/src/renderer/hooks/usePromise.ts new file mode 100644 index 0000000000..3ffb5d3405 --- /dev/null +++ b/src/renderer/hooks/usePromise.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react'; + +/** Awaits a promise and returns a loading value while the promise is unresolved + * @param promiseFactoryCallback a function that returns the promise to await. If the promise resolves to null, the value will not change. + * WARNING: MUST BE WRAPPED IN A useCallback. The reference must not be updated every render + * @param defaultValue the initial value to return while first awaiting the promise. If preserveValue is false, this value is also shown while awaiting the promise on subsequent calls. + * WARNING: MUST BE WRAPPED IN A useState, useMemo, etc. The reference must not be updated every render + * @param preserveValue whether to leave the value as the most recent resolved promise value or set it back to defaultValue while running the promise again. Default to true + * @returns [value, isLoading] the current value for the promise, either the defaultValue or the resolved promise value; whether the promise is waiting to be resolved + */ +export default ( + promiseFactoryCallback: () => Promise, + defaultValue: T, + preserveValue = true, +): [value: T, isLoading: boolean] => { + const [value, setValue] = useState(defaultValue); + const [loading, setLoading] = useState(true); + useEffect(() => { + let promiseIsCurrent = true; + setLoading(true); + (async () => { + const result = await promiseFactoryCallback(); + if (promiseIsCurrent) { + // If the promise returned null, it purposely did this to do nothing. Maybe its dependencies are not set up + if (result != null) setValue(result); + setLoading(false); + } + })(); + + return () => { + // Mark this promise as old and not to be used + promiseIsCurrent = false; + if (!preserveValue) setValue(defaultValue); + }; + }, [promiseFactoryCallback, defaultValue, preserveValue]); + + return [value, loading]; +}; diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 7e4a1a4d97..5a1d2d60aa 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -1,14 +1,12 @@ import { createRoot } from 'react-dom/client'; +import * as NetworkService from '@shared/services/NetworkService'; +import * as CommandService from '@shared/services/CommandService'; import App from './App'; -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const container = document.getElementById('root')!; -const root = createRoot(container); -root.render(); +// App-wide service setup +NetworkService.initialize(); +CommandService.initialize(); -// calling IPC exposed from preload script -window.electron?.ipcRenderer.once('ipc-example', (arg) => { - // eslint-disable-next-line no-console - console.log(arg); -}); -window.electron?.ipcRenderer.sendMessage('ipc-example', ['ping']); +const container = document.getElementById('root'); +const root = createRoot(container as HTMLElement); +root.render(); diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 1cb6557bff..3db472c9e2 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -1,8 +1,8 @@ -import { ElectronHandler } from 'main/preload'; +import { ElectronAPIHandler } from 'main/preload'; declare global { interface Window { - electron: ElectronHandler; + electronAPI: ElectronAPIHandler; } } diff --git a/src/renderer/services/ClientNetworkConnector.ts b/src/renderer/services/ClientNetworkConnector.ts new file mode 100644 index 0000000000..7ddd82a628 --- /dev/null +++ b/src/renderer/services/ClientNetworkConnector.ts @@ -0,0 +1,398 @@ +import { + ConnectionStatus, + CONNECTOR_INFO_DISCONNECTED, + InternalRequest, + InternalRequestHandler, + InternalResponse, + NetworkConnectorInfo, +} from '@shared/data/InternalConnectionTypes'; +import { Unsubscriber } from '@shared/util/PapiUtil'; +import INetworkConnector from '@shared/services/INetworkConnector'; +import { + InitClient, + Message, + MessageType, + WebSocketRequest, + WebSocketResponse, + WEBSOCKET_PORT, +} from '@shared/data/NetworkConnectorTypes'; + +// #region local variables + +// TODO: implement request timeout logic +/** Holds promises for a request */ +type LiveRequest = { + requestId: number; + resolve: ( + value: InternalResponse | PromiseLike>, + ) => void; + reject: (reason?: unknown) => void; +}; + +// #endregion + +/** + * Handles the connection from the client to the server + */ +export default class ClientNetworkConnector implements INetworkConnector { + // #region INetworkConnector members + + connectorInfo: NetworkConnectorInfo = CONNECTOR_INFO_DISCONNECTED; + + connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected; + + // #endregion + + // #region private members + + /** The webSocket connected to the server */ + private webSocket?: WebSocket; + + /** All message subscriptions - arrays of functions that run each time a message with a specific message type comes in */ + private messageSubscriptions = new Map< + MessageType, + ((eventData: Message) => void)[] + >(); + + /** Promise that resolves when the connection is finished or rejects if disconnected before the connection finishes */ + private connectPromise?: Promise; + + /** Function that removes this initClient handler from the connection */ + private unsubscribeHandleInitClientMessage?: Unsubscriber; + + /** Function that removes this response handler from the connection */ + private unsubscribeHandleResponseMessage?: Unsubscriber; + + /** Function that removes this handleRequest from the connection */ + private unsubscribeHandleRequestMessage?: Unsubscriber; + + /** + * Function to call when we receive a request that is registered on this connector. + * Handles requests from the connection and returns a response to send back + */ + private localRequestHandler?: InternalRequestHandler; + + /** + * Function to call when we are sending a request. + * Returns a clientId to which to send the request based on the requestType + */ + private requestRouter?: (requestType: string) => number; + + /** All requests that are waiting for a response */ + // Disabled no-explicit-any because assigning a request with generic type to LiveRequest gave error + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private requests = new Map>(); + + // #endregion + + // #region INetworkConnector methods + + connect = async ( + localRequestHandler: InternalRequestHandler, + requestRouter: (requestType: string) => number, + ) => { + // NOTE: This does not protect against sending two different request handlers. See ConnectionService for that + // We don't need to run this more than once + if (this.connectPromise) return this.connectPromise; + + this.connectionStatus = ConnectionStatus.Connecting; + this.localRequestHandler = localRequestHandler; + this.requestRouter = requestRouter; + + /** Function that resolves the connection promise to be run after receiving a client id */ + let resolveConnect: ( + value: NetworkConnectorInfo | PromiseLike, + ) => void; + /** Function that rejects the connection promise */ + let rejectConnect: (reason?: string) => void; + + /** The promise that resolves when the service is finished connecting */ + this.connectPromise = new Promise( + (resolve, reject) => { + resolveConnect = resolve; + rejectConnect = reject; + }, + ); + + // Set up subscriptions that the service needs to work + // Get the client id from the server on new connections + this.unsubscribeHandleInitClientMessage = this.subscribe( + MessageType.InitClient, + ({ connectorInfo: newConnectorInfo }: InitClient) => { + this.connectorInfo = newConnectorInfo; + + if (!this.webSocket) { + rejectConnect('webSocket is gone!'); + return; + } + + // Listen for responses from the server and resolve the request promise + this.unsubscribeHandleResponseMessage = this.subscribe( + MessageType.Response, + this.handleResponseMessage, + ); + + // Listen for requests from the server and run the request handler + this.unsubscribeHandleRequestMessage = this.subscribe( + MessageType.Request, + (requestMessage: WebSocketRequest) => + this.handleRequestMessage(requestMessage, true), + ); + + // Finished setting up WebSocketService and connecting! Resolve the promise + this.connectionStatus = ConnectionStatus.Connected; + resolveConnect(this.connectorInfo); + }, + ); + + // Connect the webSocket + this.webSocket = new WebSocket(`ws://localhost:${WEBSOCKET_PORT}`); + + // Attach event listeners + this.webSocket.addEventListener('message', this.onMessage); + this.webSocket.addEventListener('close', this.disconnect); + + return this.connectPromise; + }; + + notifyClientConnected = async () => { + this.sendMessage({ + type: MessageType.ClientConnect, + senderId: this.connectorInfo.clientId, + }); + // In webSocket land, we do not receive a response from the server when we notify client connected + // TODO: change the clientconnected into a request that resolves properly + return Promise.resolve(); + }; + + disconnect = () => { + if (this.connectionStatus !== ConnectionStatus.Connected) + throw new Error('Web socket closed without connecting'); + + this.connectionStatus = ConnectionStatus.Disconnected; + this.localRequestHandler = undefined; + this.connectPromise = undefined; + this.connectorInfo = CONNECTOR_INFO_DISCONNECTED; + if (this.unsubscribeHandleInitClientMessage) + this.unsubscribeHandleInitClientMessage(); + if (this.unsubscribeHandleResponseMessage) + this.unsubscribeHandleResponseMessage(); + if (this.unsubscribeHandleRequestMessage) + this.unsubscribeHandleRequestMessage(); + + if (this.webSocket) { + this.webSocket.removeEventListener('message', this.onMessage); + this.webSocket.removeEventListener('close', this.disconnect); + this.webSocket.close(); + this.webSocket = undefined; + } + }; + + request = async ( + requestType: string, + request: InternalRequest, + ): Promise> => { + // Set up a promise we can resolve later + let liveRequest: LiveRequest | undefined; + const requestPromise = new Promise>( + (resolve, reject) => { + liveRequest = { + requestId: request.requestId, + resolve, + reject, + }; + }, + ); + + if (!liveRequest) + throw new Error( + `Live request was not created for requestId ${request.requestId}`, + ); + + // Save the live request to resolve when we get the response + this.requests.set(request.requestId, liveRequest); + + // Send the request corresponding to the live request promise + this.handleRequestMessage( + { + type: MessageType.Request, + requestType, + ...request, + }, + false, + ); + + return requestPromise; + }; + + // #endregion + + // #region private methods + + /** + * Send a message to the server via webSocket. Throws if not connected + * @param message message to send + */ + // Add if needed: @param unsafe whether to get the client even if we aren't connected. WARNING: SETTING THIS FLAG MEANS NOT CHECKING FOR INITIALIZATION. DO NOT USE OUTSIDE OF INITIALIZATION. There may be no clientId + private sendMessage = (message: Message): void => { + // TODO: add message queueing + if (this.connectionStatus !== ConnectionStatus.Connected || !this.webSocket) + throw new Error( + `Trying to send message when not connected! Message ${message}`, + ); + + if ( + message.type === MessageType.Response && + this.connectorInfo.clientId === message.requesterId + ) { + // This message is from us and for us. Handle the message as if we just received it + this.onMessage( + { + data: message as unknown as string, + } as MessageEvent, + true, + ); + } else { + // This message is for someone else. Send the message + this.webSocket.send(JSON.stringify(message)); + } + }; + + /** + * Receives and appropriately publishes server webSocket messages + * @param event webSocket message information + * @param fromSelf whether this message is from this connector instead of from someone else + */ + private onMessage = (event: MessageEvent, fromSelf = false) => { + const data = fromSelf + ? (event.data as unknown as Message) + : (JSON.parse(event.data as string) as Message); + + const callbacks = this.messageSubscriptions.get(data.type); + callbacks?.forEach((callback) => callback(data)); + }; + + /** + * Unsubscribes a function from running on webSocket messages + * @param messageType the type of message from which to unsubscribe the function + * @param callback function to unsubscribe from being run on webSocket messages. + * @returns true if successfully unsubscribed + * Likely will never need to be exported from this file. Just use subscribe, which returns a matching unsubscriber function that runs this. + */ + private unsubscribe = ( + messageType: MessageType, + callback: (eventData: Message) => void, + ): boolean => { + const callbacks = this.messageSubscriptions.get(messageType); + + if (!callbacks) return false; // Did not find any callbacks for the message type + + const callbackIndex = callbacks.indexOf(callback); + if (callbackIndex < 0) return false; // Did not find this callback for the message type + + // Remove the callback + callbacks.splice(callbackIndex, 1); + + // Remove the map entry if there are no more callbacks + if (callbacks.length === 0) this.messageSubscriptions.delete(messageType); + + // Indicate successfully removed the callback + return true; + }; + + /** + * Subscribes a function to run on webSocket messages of a particular type + * @param messageType the type of message on which to subscribe the function + * @param callback function to run with the contents of the webSocket message + * @returns unsubscriber function to run to stop calling the passed-in function on webSocket messages + */ + private subscribe = ( + messageType: MessageType, + // Any is here because I dunno how to narrow Message type to a specific message type in parameters of a function + // TODO: investigate this further another time + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (eventData: Message | any) => void, + ): Unsubscriber => { + let callbacks = this.messageSubscriptions.get(messageType); + if (!callbacks) { + callbacks = []; + this.messageSubscriptions.set(messageType, callbacks); + } + callbacks.push(callback); + + return () => this.unsubscribe(messageType, callback); + }; + + /** + * Function that handles webSocket messages of type Response. + * Resolves the request associated with the received response message + * @param response Response message to resolve + */ + private handleResponseMessage = (response: WebSocketResponse) => { + const { requesterId, senderId, requestId } = response; + if (this.connectorInfo.clientId !== requesterId) + throw new Error( + `Received response from responder senderId ${senderId} with wrong requesterId ${requesterId}!`, + ); + + const liveRequest = this.requests.get(requestId); + if (!liveRequest) + throw new Error( + `Received response from responder senderId ${senderId} for nonexistent requestId ${requestId}`, + ); + + // Remove the request from the requests because it is receiving a response + this.requests.delete(requestId); + + // Run the request's response function with the response + liveRequest.resolve(response); + }; + + /** + * Function that handles incoming webSocket messages and locally sent messages of type Request. + * Runs the requestHandler provided in connect() and sends a message with the response + * @param requestMessage request message to handle + * @param isIncoming whether this message is coming from the server and we should definitely handle it locally + * or if it is a locally sent request and we should send to the server if we don't have a local handler + */ + private handleRequestMessage = async ( + requestMessage: WebSocketRequest, + isIncoming: boolean, + ) => { + if (!this.requestRouter) + throw new Error( + `Received a request but cannot route it without a requestRouter`, + ); + + // Figure out if we can handle this request or if we need to send it + // We should handle it here if it came from the server (which means the server thinks we have a handler for it) + // or if we actually have a handler for it. Otherwise send the request to the server + if ( + isIncoming || + this.connectorInfo.clientId === + this.requestRouter(requestMessage.requestType) + ) { + // This request is ours. Handle the request + if (!this.localRequestHandler) + throw Error('Handling request without a requestHandler!'); + + // Run the request handler for this request + const response = await this.localRequestHandler( + requestMessage.requestType, + requestMessage, + ); + + // Send the response to this request + this.sendMessage({ + type: MessageType.Response, + requestType: requestMessage.requestType, + ...response, + }); + } else { + // This request is for someone else. Send the request to the server to handle/forward + this.sendMessage(requestMessage); + } + }; + + // #endregion +} diff --git a/src/shared/data/InternalConnectionTypes.ts b/src/shared/data/InternalConnectionTypes.ts new file mode 100644 index 0000000000..15beb630e5 --- /dev/null +++ b/src/shared/data/InternalConnectionTypes.ts @@ -0,0 +1,59 @@ +/** + * Types that are internal to the communication we do through WebSocket. + * These types should not need to be used outside of NetworkConnectors and ConnectionService.ts + */ + +import { ComplexRequest, ComplexResponse } from '@shared/util/PapiUtil'; + +/** Represents when the client id has not been assigned by the server */ +export const CLIENT_ID_UNASSIGNED = -1; + +/** "Client id" for the server */ +export const CLIENT_ID_SERVER = 0; + +/** Represents when the connector info has not been populated by the server */ +export const CONNECTOR_INFO_DISCONNECTED = Object.freeze({ + clientId: CLIENT_ID_UNASSIGNED, +}); + +/** Information about the network connector */ +export type NetworkConnectorInfo = { + clientId: number; +}; + +/** Whether this connector is setting up or has finished setting up its connection and is ready to communicate on the network */ +export enum ConnectionStatus { + /** This connector is not connected to the network */ + Disconnected, + /** This connector is attempting to connect to the network and retrieve connectorInfo */ + Connecting, + /** This connector has finished setting up its connection - has connectorInfo and such */ + Connected, +} + +/** Request to do something and to respond */ +export type InternalRequest = { + senderId: number; + requestId: number; +} & ComplexRequest; + +/** Response to a request */ +export type InternalResponse = { + /** The process that sent this Response */ + senderId: number; + requestId: number; + /** The process that originally sent the Request that matches to this response */ + requesterId: number; +} & ComplexResponse; + +/** Handler for requests from the server */ +export type InternalRequestHandler = ( + requestType: string, + request: InternalRequest, +) => Promise>; + +/** Handler for requests from the server */ +export type RequestHandler = ( + requestType: string, + request: ComplexRequest, +) => Promise>; diff --git a/src/shared/data/NetworkConnectorTypes.ts b/src/shared/data/NetworkConnectorTypes.ts new file mode 100644 index 0000000000..75709097f4 --- /dev/null +++ b/src/shared/data/NetworkConnectorTypes.ts @@ -0,0 +1,55 @@ +/** + * Types that are relevant particularly to the implementation of communication on NetworkConnector.ts files + * Do not use these types outside of ClientNetworkConnector.ts and ServerNetworkConnector.ts + */ + +import { + InternalRequest, + InternalResponse, + NetworkConnectorInfo, +} from '@shared/data/InternalConnectionTypes'; + +/** Port to use for the webSocket */ +export const WEBSOCKET_PORT = 8876; + +/** WebSocket message type that indicates how to handle it */ +export enum MessageType { + InitClient = 'init-client', + ClientConnect = 'client-connect', + Request = 'request', + Response = 'response', +} + +/** Message sent to the client to give it NetworkConnectorInfo */ +export type InitClient = { + type: MessageType.InitClient; + senderId: number; + connectorInfo: NetworkConnectorInfo; +}; + +/** Message responding to the server to let it know this connection is ready to receive messages */ +export type ClientConnect = { + type: MessageType.ClientConnect; + senderId: number; +}; + +/** Request to do something and to respond */ +export type WebSocketRequest = { + type: MessageType.Request; + /** What kind of request this is. Certain command, event, etc */ + requestType: string; +} & InternalRequest; + +/** Response to a request */ +export type WebSocketResponse = { + type: MessageType.Response; + /** What kind of request this is. Certain command, event, etc */ + requestType: string; +} & InternalResponse; + +/** Messages send by the WebSocket */ +export type Message = + | InitClient + | ClientConnect + | WebSocketRequest + | WebSocketResponse; diff --git a/src/shared/services/CommandService.ts b/src/shared/services/CommandService.ts new file mode 100644 index 0000000000..5c97b6b934 --- /dev/null +++ b/src/shared/services/CommandService.ts @@ -0,0 +1,147 @@ +/** + * Handles registering, sending, and receiving commands with the Paratext backend in a unified format. + * Exposed on papi + */ + +import memoizeOne from 'memoize-one'; +import * as NetworkService from '@shared/services/NetworkService'; +import { + aggregateUnsubscriberAsyncs, + CATEGORY_COMMAND, + CommandHandler, + createSafeRegisterFn, + serializeRequestType, + UnsubPromiseAsync, +} from '@shared/util/PapiUtil'; +import { isClient } from '@shared/util/InternalUtil'; + +/** Whether this service has finished setting up */ +let isInitialized = false; + +/** Registration object for a command. Want an object so we can register multiple commands at once */ +// Any is probably fine because we likely never know or care about the args or return +export type CommandRegistration< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TParam extends Array = any[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TReturn = any, +> = { + commandName: string; + handler: CommandHandler; +}; + +async function addThree(a: number, b: number, c: number) { + return a + b + c; +} +async function squareAndConcat(a: number, b: string) { + return a * a + b.toString(); +} +/** Commands that this process will handle. Registered automatically at initialization */ +const commandFunctions: { [commandName: string]: CommandHandler } = { + addThree, + squareAndConcat, +}; + +/** + * Send a command to the backend. + * WARNING: THIS DOES NOT CHECK FOR INITIALIZATION. DO NOT USE OUTSIDE OF INITIALIZATION. Use sendCommand + */ +const sendCommandUnsafe = async , TReturn>( + commandName: string, + ...args: TParam +): Promise => { + return NetworkService.request( + serializeRequestType(CATEGORY_COMMAND, commandName), + ...args, + ); +}; + +/** + * Register a command on the papi to be handled here. + * WARNING: THIS DOES NOT CHECK FOR INITIALIZATION. DO NOT USE OUTSIDE OF INITIALIZATION. Use registerCommand + * @param commandName command name to register for handling here + * @param handler function to run when the command is invoked + * @returns promise that resolves if the request successfully registered and unsubscriber function to run to stop the passed-in function from handling requests + */ +export const registerCommandUnsafe = ( + commandName: string, + handler: CommandHandler, +): UnsubPromiseAsync => { + return NetworkService.registerRequestHandler( + serializeRequestType(CATEGORY_COMMAND, commandName), + handler, + ); +}; + +/** Sets up the CommunicationService */ +export const initialize = memoizeOne(async (): Promise => { + if (isInitialized) return; + + // TODO: Might be best to make a singleton or something + await NetworkService.initialize(); + + // Set up subscriptions that the service needs to work + + // Register built-in commands + if (isClient()) { + // TODO: make a registerRequestHandlers function that we use here and in NetworkService.initialize? + const unsubPromises = Object.entries(commandFunctions).map( + ([commandName, handler]) => registerCommandUnsafe(commandName, handler), + ); + + const unsubscribeCommands = aggregateUnsubscriberAsyncs( + unsubPromises.map(({ unsubscriber }) => unsubscriber), + ); + + // Wait to successfully register all commands + await Promise.all(unsubPromises.map(({ promise }) => promise)); + + // On closing, try to remove command listeners + // TODO: should do this on the server when the connection closes or when the server exists as well + window.addEventListener('beforeunload', async () => { + await unsubscribeCommands(); + }); + } + + isInitialized = true; + + if (isClient()) { + const start = performance.now(); + sendCommandUnsafe('echo', 'Hi server!') + .then((response) => + console.log( + 'command:echo Response!!!', + response, + 'Response time:', + performance.now() - start, + ), + ) + .catch((e) => console.error(e)); + } +}); + +/** + * Send a command to the backend. + */ +export const sendCommand = async , TReturn>( + commandName: string, + ...args: TParam +): Promise => { + await initialize(); + return sendCommandUnsafe(commandName, ...args); +}; + +/** + * Register a command on the papi to be handled here + * @param commandName command name to register for handling here + * @param handler function to run when the command is invoked + * @returns true if successfully registered, throws with error message if not + */ +export const registerCommand: ( + commandName: string, + handler: CommandHandler, +) => UnsubPromiseAsync = createSafeRegisterFn( + registerCommandUnsafe, + isInitialized, + initialize, +); diff --git a/src/shared/services/ConnectionService.ts b/src/shared/services/ConnectionService.ts new file mode 100644 index 0000000000..7cabc57a74 --- /dev/null +++ b/src/shared/services/ConnectionService.ts @@ -0,0 +1,226 @@ +/** + * Handles setting up a connection to the electron backend and exchanging simple messages. + * Do not use outside NetworkService.ts. For communication, use NetworkService.ts as it is an abstraction over this. + */ +// TODO: Refactor into a class and an interface +// TODO: Combine with NetworkSerice? + +import { + CLIENT_ID_UNASSIGNED, + ConnectionStatus, + InternalRequest, + InternalRequestHandler, + InternalResponse, + RequestHandler, +} from '@shared/data/InternalConnectionTypes'; +import INetworkConnector from '@shared/services/INetworkConnector'; +import * as NetworkConnectorFactory from '@shared/services/NetworkConnectorFactory'; +import { ComplexResponse } from '@shared/util/PapiUtil'; + +/** Whether this connector is setting up or has finished setting up its connection and is ready to communicate on the network */ +let connectionStatus = ConnectionStatus.Disconnected; +/** The client id for this browser as assigned by the server */ +let clientId = CLIENT_ID_UNASSIGNED; +/** The next requestId to use for identifying requests */ +let nextRequestId = 0; + +/** Promise that resolves when the connection is finished or rejects if disconnected before the connection finishes */ +let connectPromise: Promise | undefined; +/** Function that resolves the connection promise to be run after receiving a client id */ +let connectResolve: (() => void) | undefined; +/** Function that rejects the connection promise */ +let connectReject: ((reason?: string) => void) | undefined; +/** Function that accepts requests from the server and responds accordingly. From connect() */ +let requestHandler: RequestHandler | undefined; +/** Function that determines the appropriate clientId to which to send requests of the given type. From connect() */ +let requestRouter: ((requestType: string) => number) | undefined; +/** The network connector this service uses to send and receive messages */ +let networkConnector: INetworkConnector | undefined; + +/** + * Send a request to the server and resolve after receiving a response + * @param requestType the type of request + * @param contents contents to send in the request + * @returns promise that resolves with the response message + */ +export const request = async ( + requestType: string, + contents: TParam, +): Promise> => { + if (!networkConnector) throw Error('request without a networkConnector!'); + + // TODO: move the request and clientId code into the NetworkConnector? Leaving for now since it is currently shared between the implementations + const requestId = nextRequestId; + nextRequestId += 1; + + // TODO: implement request timeout logic? + const response = await networkConnector.request( + requestType, + { + requestId, + senderId: clientId, + contents, + }, + ); + + if (requestId !== response.requestId) + throw new Error( + `Received response from ${response.senderId} with wrong requestId! requestId = ${requestId}, response.requestId = ${response.requestId}`, + ); + + if (clientId !== response.requesterId) + throw new Error( + `Received response from ${response.senderId} with wrong requesterId ${response.requesterId}!`, + ); + + return response; +}; + +/** Disconnects from the server */ +export const disconnect = () => { + requestHandler = undefined; + requestRouter = undefined; + connectPromise = undefined; + if (networkConnector) { + networkConnector.disconnect(); + networkConnector = undefined; + } + if (connectionStatus !== ConnectionStatus.Connected && connectReject) + connectReject('Disconnecting - client never finished connecting'); + connectResolve = undefined; + connectReject = undefined; + connectionStatus = ConnectionStatus.Disconnected; +}; + +/** + * Function that handles internal requests by running the requestHandler given in connect() + * @param requestType type of request to determine which handler to use + * @param incomingRequest request message to handle + * @returns response message for the request + */ +const handleInternalRequest: InternalRequestHandler = async ( + requestType: string, + incomingRequest: InternalRequest, +) => { + if (!requestHandler) + throw Error('Handling request without a requestHandler!'); + + // Not sure if it's really responsible to put the whole incomingRequest in. Might want to destructure and just pass ComplexRequest members + const response = await requestHandler( + requestType, + incomingRequest, + ); + return { + ...response, + senderId: clientId, + requesterId: incomingRequest.senderId, + requestId: incomingRequest.requestId, + } as InternalResponse; +}; + +/** + * Sets up the ConnectionService by connecting to the server and setting up event handlers + * @param networkRequestHandler function that handles requests from the server by accepting a requestType and a ComplexRequest and returning a Promise of a Complex Response + * @returns Promise that resolves when finished connecting + */ +export const connect = async ( + networkRequestHandler: RequestHandler, + networkRequestRouter: (requestType: string) => number, +): Promise => { + // Do not run anything asynchronous before we create and assign connectPromise below! + // We must assign connectPromise immediately so we do not run connect multiple times at once + + // We don't need to run this more than once + if (connectPromise /* connecting || connected */) { + if ( + networkRequestHandler === requestHandler && + networkRequestRouter === requestRouter + ) + return connectPromise; + throw new Error( + 'Cannot connect with two different request handlers or request routers', + ); + } + + if (!networkRequestHandler) throw new Error('Must provide a request handler'); + if (!networkRequestRouter) throw new Error('Must provide a request router'); + + // Start connecting + connectionStatus = ConnectionStatus.Connecting; + connectPromise = new Promise((resolve, reject) => { + connectResolve = resolve; + connectReject = reject; + }); + requestHandler = networkRequestHandler; + requestRouter = networkRequestRouter; + + // Set up subscriptions that the service needs to work + + // Create the network connector + try { + networkConnector = await NetworkConnectorFactory.createNetworkConnector(); + } catch (e) { + connectionStatus = ConnectionStatus.Disconnected; + connectPromise = undefined; + const err = `ConnectionService: Failed to creacte NetworkConnection object: ${e}`; + if (connectReject) connectReject(err); + throw new Error(err); + } + + // Set up the connection and get the client id from the server on new connections + try { + if (!requestRouter) throw new Error('requestRouter not defined.'); + + const newConnectorInfo = await networkConnector.connect( + handleInternalRequest, + requestRouter, + ); + + if (clientId !== CLIENT_ID_UNASSIGNED) { + if (!connectReject) + throw new Error( + 'connectReject not defined. Not connecting? But we already have a clientId', + ); + connectReject( + `Received clientId when already assigned! Current clientId: ${clientId}. New clientId: ${newConnectorInfo}`, + ); + return undefined; + } + + clientId = newConnectorInfo.clientId; + console.log(`Got clientId ${clientId}`); + + if (!networkConnector) { + if (!connectReject) + throw new Error( + 'connectReject not defined and networkConnector not defined.', + ); + connectReject('networkConnector not defined'); + return undefined; + } + + // Finished setting up and connecting! Resolve the promise + if (!connectResolve) + throw new Error( + 'connectResolve not defined. Tried to connect but somehow this is undefined', + ); + + // Server is not able to send us requests until we are finished connecting + connectionStatus = ConnectionStatus.Connected; + connectResolve(); + + // Notify server that we are finished connecting + networkConnector.notifyClientConnected(); + } catch (e) { + connectionStatus = ConnectionStatus.Disconnected; + connectPromise = undefined; + const err = `ConnectionService: Connecting and getting clientId failed: ${e}`; + if (connectReject) connectReject(err); + throw new Error(err); + } + + return connectPromise; +}; + +/** Gets this connection's clientId */ +export const getClientId = () => clientId; diff --git a/src/shared/services/INetworkConnector.ts b/src/shared/services/INetworkConnector.ts new file mode 100644 index 0000000000..3190cebc07 --- /dev/null +++ b/src/shared/services/INetworkConnector.ts @@ -0,0 +1,58 @@ +import { + ConnectionStatus, + InternalRequestHandler, + NetworkConnectorInfo, +} from '@shared/data/InternalConnectionTypes'; + +/** + * Interface that defines the network connection functionality the server and the client must implement. + * Used by NetworkConnectorFactory to supply the right kind of NetworkConnector to ConnectionService + */ +export default interface INetworkConnector { + /** Information about the connector. Populated by the server while connecting */ + connectorInfo: NetworkConnectorInfo; + + /** Whether this connector is setting up or has finished setting up its connection and is ready to communicate on the network */ + connectionStatus: ConnectionStatus; + + /** + * Sets up the NetworkConnector by populating connector info, setting up event handlers, and doing one of the following: + * - On Client: connecting to the server. + * - On Server: opening an endpoint for clients to connect. + * MUST ALSO RUN notifyClientConnected() WHEN PROMISE RESOLVES + * @param localRequestHandler function that handles requests from the connection. Only called when this connector can handle the request + * @param requestRouter function that returns a clientId to which to send the request based on the requestType. If requestRouter returns this connector's clientId, localRequestHandler is used + * @returns Promise that resolves with connector info when finished connecting + */ + connect: ( + localRequestHandler: InternalRequestHandler, + requestRouter: (requestType: string) => number, + ) => Promise; + /** + * Notify the server that this client has received its connectorInfo and is ready to go. + * MUST RUN AFTER connect() WHEN ITS PROMISE RESOLVES + * TODO: Is this necessary? + */ + notifyClientConnected: () => Promise; + /** + * Disconnects from the connection: + * - On Client: disconnects from the server + * - On Server: disconnects from clients and closes its connection endpoint + */ + disconnect: () => void; + /** + * Send a request to the server/a client and resolve after receiving a response + * @param requestType the type of request + * @param contents contents to send in the request + * @returns promise that resolves with the response message + */ + request: InternalRequestHandler; + /** + * Register a handler on this connection to run when it receives a request. + * Not needed as we are passing in the requestHandler with connect(). + * But should we make onRequest run callbacks that want to listen for requests? + * @param callback handler to run with request from requestor, async returns a response to the requestor + * @returns unsubscriber to remove this handler from running on requests + */ + /* onRequest: (callback: InternalRequestHandler) => Unsubscriber; */ +} diff --git a/src/shared/services/NetworkConnectorFactory.ts b/src/shared/services/NetworkConnectorFactory.ts new file mode 100644 index 0000000000..7fa124511b --- /dev/null +++ b/src/shared/services/NetworkConnectorFactory.ts @@ -0,0 +1,24 @@ +/** + * Creates a ServerNetworkConnector or a ClientNetworkConnector depending on if we're in main or renderer + */ + +import { isClient } from '@shared/util/InternalUtil'; +import INetworkConnector from '@shared/services/INetworkConnector'; + +/** + * Creates a NetworkConnector for the client or the server depending on where you're running + * @returns NetworkConnector + */ +// eslint-disable-next-line import/prefer-default-export +export const createNetworkConnector = async (): Promise => { + if (isClient()) { + const ClientNetworkConnector = ( + await import('@renderer/services/ClientNetworkConnector') + ).default; + return new ClientNetworkConnector(); + } + const ServerNetworkConnector = ( + await import('@main/services/ServerNetworkConnector') + ).default; + return new ServerNetworkConnector(); +}; diff --git a/src/shared/services/NetworkService.ts b/src/shared/services/NetworkService.ts new file mode 100644 index 0000000000..a039d0e2e4 --- /dev/null +++ b/src/shared/services/NetworkService.ts @@ -0,0 +1,554 @@ +/** + * Handles requests, responses, subscriptions, etc. to the backend. + * Likely shouldn't need/want to expose on papi + */ + +import memoizeOne from 'memoize-one'; +import { + CLIENT_ID_SERVER, + RequestHandler, +} from '@shared/data/InternalConnectionTypes'; +import { + aggregateUnsubscriberAsyncs, + CommandHandler, + ComplexRequest, + ComplexResponse, + createSafeRegisterFn, + UnsubPromiseAsync, + UnsubscriberAsync, +} from '@shared/util/PapiUtil'; +import { getErrorMessage } from '@shared/util/Util'; +import * as ConnectionService from '@shared/services/ConnectionService'; +import { isClient } from '@shared/util/InternalUtil'; + +/** Whether this service has finished setting up */ +let isInitialized = false; +/** Map of requestType to registered handler for that request or (on server) information about which connection to send the request */ +const requestRegistrations = new Map(); + +/** Request handler that is a local function and can be handled locally */ +type LocalRequestRegistration = { + registrationType: 'local'; + requestType: string; + handlerType: RequestHandlerType; + handler: + | RoutedRequestHandler + | RoutedRequestHandler; +}; + +/** Request handler that is not on this network service and must be requested on the network. Server-only as clients will all just send to the server */ +type RemoteRequestRegistration = { + registrationType: 'remote'; + requestType: string; + clientId: number; +}; + +/** Information about the request handler and how to run it */ +// Any is probably fine because we likely never know or care about the args or return +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RequestRegistration = + | LocalRequestRegistration + | RemoteRequestRegistration; + +/** + * Args handler function for a request. Called when a request is handled. + * The function should accept the spread of the contents array of the request as its parameters. + * The function should return an object that becomes the contents object of the response. + * This type of handler is a normal function. + */ +type ArgsRequestHandler< + // Any is probably fine because we likely never know or care about the args or return + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TParam extends Array = any[], + // Any is probably fine because we likely never know or care about the args or return + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TReturn = any, +> = CommandHandler; + +/** + * Contents handler function for a request. Called when a request is handled. + * The function should accept the contents object of the request as its single parameter. + * The function should return an object that becomes the contents object of the response. + */ +// Any is probably fine because we likely never know or care about the args or return +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ContentsRequestHandler = ( + contents: TParam, +) => Promise; + +/** + * Complex handler function for a request. Called when a request is handled. + * The function should accept a ComplexRequest object as its single parameter. + * The function should return a ComplexResponse object that becomes the response.. + * This type of handler is the most flexible of the request handlers. + */ +// Any is probably fine because we likely never know or care about the args or return +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ComplexRequestHandler = ( + request: ComplexRequest, +) => Promise>; + +/** Handler function for a request */ +// Any is probably fine because we likely never know or care about the args or return +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RoutedRequestHandler = + | ArgsRequestHandler + | ContentsRequestHandler + | ComplexRequestHandler; + +/** Type of request handler - indicates what type of parameters and what return type the handler has */ +enum RequestHandlerType { + Args = 'args', + Contents = 'contents', + Complex = 'complex', +} + +// #region Private unsafe functions (do not call manually outside of initialization) + +/** + * Send a request to the server and resolve a ComplexResponse after receiving a response. + * Note: Unless you need access to ComplexResponse properties, you probably just want to use request + * WARNING: THIS THROWS IF NOT INITIALIZED. DO NOT USE OUTSIDE OF INITIALIZATION. Use requestRaw + * @param requestType the type of request + * @param contents contents to send in the request + * @returns promise that resolves with the response message + */ +const requestRawUnsafe = async ( + requestType: string, + contents: TParam, +): Promise> => { + if (!isInitialized) + throw new Error( + `Cannot perform raw request ${requestType} as the NetworkService is not initialized`, + ); + return ConnectionService.request(requestType, contents); +}; + +/** + * Send a request on the network and resolve the response contents. + * WARNING: THIS THROWS IF NOT INITIALIZED. DO NOT USE OUTSIDE OF INITIALIZATION. Use request + * @param requestType the type of request + * @param args arguments to send in the request (put in request.contents) + * @returns promise that resolves with the response message + */ +const requestUnsafe = async , TReturn>( + requestType: string, + ...args: TParam +) => { + if (!isInitialized) + throw new Error( + `Cannot perform request ${requestType} as the NetworkService is not initialized`, + ); + const response = await requestRawUnsafe(requestType, args); + if (!response.success) throw new Error(response.errorMessage); + return response.contents; +}; + +/** + * Unregisters a local request handler from running on requests. + * WARNING: THIS THROWS IF NOT INITIALIZED. DO NOT USE OUTSIDE OF INITIALIZATION. Use unregisterRequestHandler (not created yet as it may never be necessary) + * @param requestType the type of request from which to unregister the handler + * @param handler function to unregister from running on requests + * @returns true if successfully unregistered, false if registration not found or trying to unregister a handler that is not local. Throws if provided handler is not the correct handler + * Likely will never need to be exported from this file. Just use registerRequestHandler, which returns a matching unsubscriber function that runs this. + */ +async function unregisterRequestHandlerUnsafe( + requestType: string, + handler: RoutedRequestHandler, +): Promise { + const requestRegistration = requestRegistrations.get(requestType); + + if (!requestRegistration) + // The request isn't registered locally + // TODO: If you run the unsubscribe from registerRequestHandler before it resolves, you + // may accidentally hit here before the handler is registered. That would be a memory + // leak, I suppose. We should consider a better solution + return false; + + if (requestRegistration.registrationType === 'remote') + // The request handler is someone else's to unregister. Is this egregious enough that we should throw here? This only really happens if you're the server right now as the server holds remote handlers + return false; + + if (requestRegistration.handler !== handler) + // Somehow the handlers do not match. Probably can't happen unless you call this function directly which shouldn't happen. Is this egregious enough that we should throw? I guess...? + throw new Error( + `Handler to unsubscribe from ${requestType} does not match registered handler`, + ); + + // Check with the server to make sure we can unregister this registration + const remoteUnregisterSuccessful = isClient() + ? await requestUnsafe( + 'server:unregisterRequest', + requestType, + ConnectionService.getClientId(), + ) + : true; + + if (!remoteUnregisterSuccessful) + // The server did not allow us to unregister + return false; + + // We can unregister this handler! Remove it from the registrations + requestRegistrations.delete(requestType); + return true; +} + +/** + * Register a local request handler to run on requests. + * WARNING: THIS THROWS IF NOT INITIALIZED. DO NOT USE OUTSIDE OF INITIALIZATION. Use unregisterRequestHandler (not created yet as it may never be necessary) + * @param requestType the type of request on which to register the handler + * @param handler function to register to run on requests + * @param handlerType type of handler function - indicates what type of parameters and what return type the handler has + * @returns promise that resolves if the request successfully registered and unsubscriber function to run to stop the passed-in function from handling requests + */ +function registerRequestHandlerUnsafe( + requestType: string, + handler: ArgsRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +function registerRequestHandlerUnsafe( + requestType: string, + handler: ContentsRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +function registerRequestHandlerUnsafe( + requestType: string, + handler: ComplexRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +function registerRequestHandlerUnsafe( + requestType: string, + handler: RoutedRequestHandler, + handlerType = RequestHandlerType.Args, +): UnsubPromiseAsync { + let resolveRegistration: + | ((value: void | PromiseLike) => void) + | undefined; + let rejectRegistration: ((reason: string) => void) | undefined; + /** Promise that resolves when this request successfully finishes registering */ + const promise = new Promise((resolve, reject) => { + resolveRegistration = resolve; + rejectRegistration = reject; + }); + // Typescript does not understand these are definitely defined because the promise above is synchronous + if (!resolveRegistration || !rejectRegistration) + throw new Error(`Somehow the promise functions are not defined`); + // Only register the first handler provided for this request type + // Check locally if we already have a handler for this requestType + if (requestRegistrations.has(requestType)) { + // This rejectRegistration before remoteRequest so we can reject the promise returned instead + // of throwing an error is the only reason we can't just return remoteRequest and avoid all + // the complication of holding promise resolve and reject and all this. Consider just + // throwing an exception. That would mean you would have to check for registerRequestHandler + // to throw exceptions in addition to .catch-ing its promise, but maybe it's worth it. Dunno + rejectRegistration( + `requestType ${requestType} already has a local handler registered`, + ); + return { promise, unsubscriber: async () => false }; + } + + // Check with the server if it already has a handler for this requestType + const remoteRequest: Promise = isClient() + ? // If we are the client, try to register with the server because server has all registrations + requestUnsafe( + 'server:registerRequest', + requestType, + ConnectionService.getClientId(), + ) + : // If we are the server, we just checked if there was already a registration + Promise.resolve(); + + remoteRequest + .then(() => { + // We have successfully checked that this is the first registration for this requestType, set up the handler + requestRegistrations.set(requestType, { + registrationType: 'local', + requestType, + handler, + handlerType, + }); + if (!resolveRegistration) + throw new Error(`Somehow resolveRegistration is not defined`); + resolveRegistration(); + return undefined; + }) + .catch((e) => { + if (!rejectRegistration) + throw new Error(`Somehow rejectRegistration is not defined`); + rejectRegistration(e); + }); + + return { + promise, + unsubscriber: () => unregisterRequestHandlerUnsafe(requestType, handler), + }; +} + +// #endregion + +// #region Server-only variables and functions + +/** + * Unregisters a client connection's request handler + * SERVER-ONLY. This should not be needed on the client + * @param requestType the type of request on which to unregister the handler + * @param clientId clientId of the client who wants to unregister the handler + * @returns true if successfully unregistered, false otherwise + */ +const unregisterRemoteRequestHandler = async ( + requestType: string, + clientId: number, +): Promise => { + const requestRegistration = requestRegistrations.get(requestType); + + if (!requestRegistration) + // The request isn't registered + return false; + + if ( + requestRegistration.registrationType === 'local' || + requestRegistration.clientId !== clientId + ) + // The request handler is not theirs to unregister. Is this egregious enough that we should throw here? + return false; + + // We can unregister this handler! Remove it from the registrations + requestRegistrations.delete(requestType); + return true; +}; + +/** + * Registers a client connection's request handler + * SERVER-ONLY. This should not be needed on the client + * @param requestType the type of request on which to register the handler + * @param clientId clientId of the client who wants to register the handler + */ +const registerRemoteRequestHandler = async ( + requestType: string, + clientId: number, +): Promise => { + // TODO: Consider a good way to expose senderId in this scenario instead of just passing it as an argument. + // Maybe create a registerRequestHandlerInternal function that uses InternalRequest and InternalResponse? + + // Check to see if there is already a handler for this requestType + if (requestRegistrations.has(requestType)) { + throw new Error( + `requestType ${requestType} already has a remote handler registered`, + ); + } + + // Once we have checked that this is the first registration for this requestType, set up the handler + requestRegistrations.set(requestType, { + registrationType: 'remote', + requestType, + clientId, + }); +}; + +/** Map of requestTypes to server-side handlers for those requests */ +const serverRequestHandlers = { + 'server:registerRequest': registerRemoteRequestHandler, + 'server:unregisterRequest': unregisterRemoteRequestHandler, +}; +/** Function that unsubscribes all the server request handlers */ +let unsubscribeServerRequestHandlers: UnsubscriberAsync | undefined; + +// #endregion + +/** + * Calls the appropriate request handler according to the request type and returns a promise of the response + * @param requestType type of request to handle + * @param request the request to handle + * @returns promise of response to the request + */ +const handleRequestLocal: RequestHandler = async ( + requestType: string, + incomingRequest: ComplexRequest, +): Promise> => { + const registration = requestRegistrations.get(requestType); + + // Result should always be defined if success is true (and not defined if success is false), which seems to be the case in this function. + // However, for some reason, TypeScript can't seem to tell that result is defined if success is true. + // So we will just coerce it to start undefined but pretend it's TReturn. + let result: TReturn = undefined as unknown as TReturn; + let success = false; + let errorMessage = ''; + + if (!registration) + // There is no handler registered for this request. Respond failure + errorMessage = `No handler was found to process the request of type ${requestType}`; + else if (registration.registrationType === 'remote') + errorMessage = `Requested to handle local request but request ${requestType} is remote`; + else + switch (registration.handlerType) { + case RequestHandlerType.Args: + try { + result = await (incomingRequest.contents + ? (registration.handler as ArgsRequestHandler)( + ...(incomingRequest.contents as unknown as unknown[]), + ) + : (registration.handler as ArgsRequestHandler)()); + success = true; + } catch (e) { + errorMessage = getErrorMessage(e); + } + break; + case RequestHandlerType.Contents: + try { + result = await (registration.handler as ContentsRequestHandler)( + incomingRequest.contents, + ); + success = true; + } catch (e) { + errorMessage = getErrorMessage(e); + } + break; + case RequestHandlerType.Complex: { + try { + const response = await ( + registration.handler as ComplexRequestHandler + )(incomingRequest); + // Break out the contents of the ComplexResponse to use existing variables. Should we destructure instead to future-proof for other fields? It was not playing well with Typescript + success = response.success; + if (response.success) result = response.contents; + else errorMessage = response.errorMessage; + } catch (e) { + errorMessage = getErrorMessage(e); + } + break; + } + default: + throw Error( + `RequestHandlerType.${registration.handlerType} not supported! On requestType ${requestType}`, + ); + } + + if (!success && !errorMessage) { + errorMessage = `The JS-handled request of type ${requestType} was not handled successfully`; + } + + return { + contents: result, + success, + errorMessage, + }; +}; + +/** + * Determines the appropriate clientId to which to send requests of the given type + * @param requestType type of request to determine which clientId will handle the request + * @returns clientId that handles requests of the given type + */ +const routeRequest = (requestType: string): number => { + const registration = requestRegistrations.get(requestType); + if (!registration) + // We are the client and we need to send the request to the server or we are the server and we need to return an error + return CLIENT_ID_SERVER; + if (registration.registrationType === 'local') + // We will handle this request here + return ConnectionService.getClientId(); + // This registration is for another connection + return registration.clientId; +}; + +/** Sets up the NetworkService. Runs only once */ +export const initialize = memoizeOne(async (): Promise => { + if (isInitialized) return; + + // Wait to connect to the server + await ConnectionService.connect(handleRequestLocal, routeRequest); + + // Register server-only request handlers + if (!isClient()) { + const registrationUnsubAndPromises = Object.entries( + serverRequestHandlers, + ).map(([requestType, handler]) => + registerRequestHandlerUnsafe(requestType, handler), + ); + unsubscribeServerRequestHandlers = aggregateUnsubscriberAsyncs( + registrationUnsubAndPromises.map(({ unsubscriber }) => unsubscriber), + ); + // Wait to successfully register all requests + await Promise.all( + registrationUnsubAndPromises.map(({ promise }) => promise), + ); + } + + // On closing, try to close the connection + // TODO: should do this on the server when the connection closes or when the server exists as well + if (isClient()) + window.addEventListener('beforeunload', async () => { + ConnectionService.disconnect(); + if (unsubscribeServerRequestHandlers) unsubscribeServerRequestHandlers(); + }); + + isInitialized = true; +}); + +// #region Public safe functions (call these, not the private unsafe functions above) + +/** + * Send a request on the network and resolve the response contents + * @param requestType the type of request + * @param args arguments to send in the request (put in request.contents) + * @returns promise that resolves with the response message + */ +export const request = async , TReturn>( + requestType: string, + ...args: TParam +) => { + await initialize(); + return requestUnsafe(requestType, ...args); +}; + +/** Helper function so we can overload registerRequestHandler */ +const registerRequestHandlerInternal = createSafeRegisterFn( + registerRequestHandlerUnsafe, + isInitialized, + initialize, + unregisterRequestHandlerUnsafe, +); +/** + * Register a local request handler to run on requests. + * @param requestType the type of request on which to register the handler + * @param handler function to register to run on requests + * @param handlerType type of handler function - indicates what type of parameters and what return type the handler has + * @returns promise that resolves if the request successfully registered and unsubscriber function to run to stop the passed-in function from handling requests + */ +export function registerRequestHandler( + requestType: string, + handler: ArgsRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +export function registerRequestHandler( + requestType: string, + handler: ContentsRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +export function registerRequestHandler( + requestType: string, + handler: ComplexRequestHandler, + handlerType?: RequestHandlerType, +): UnsubPromiseAsync; +export function registerRequestHandler( + requestType: string, + handler: RoutedRequestHandler, + handlerType = RequestHandlerType.Args, +): UnsubPromiseAsync { + return registerRequestHandlerInternal(requestType, handler, handlerType); +} + +// #endregion + +/** + * Creates a function that is a request function with a baked requestType. + * This is also nice because you get TypeScript type support using this function. + * @param requestType requestType for request function + * @returns function to call with arguments of request that performs the request and resolves with the response contents + */ +export const createRequestFunction = , TReturn>( + requestType: string, +) => { + return async (...args: TParam) => + request(requestType, ...args); +}; diff --git a/src/shared/services/papi.ts b/src/shared/services/papi.ts new file mode 100644 index 0000000000..95ce20eaf0 --- /dev/null +++ b/src/shared/services/papi.ts @@ -0,0 +1,7 @@ +/** + * Unified module for accessing API features in extensions + */ + +import * as CommandService from '@shared/services/CommandService'; + +export default { commands: CommandService }; diff --git a/src/shared/util/InternalUtil.ts b/src/shared/util/InternalUtil.ts new file mode 100644 index 0000000000..4dab1751f8 --- /dev/null +++ b/src/shared/util/InternalUtil.ts @@ -0,0 +1,12 @@ +/** + * Utility functions specific to the internal technologies we are using. + */ + +/** + * Determine if running on the client or on the server. + * Thanks to Marshal at https://stackoverflow.com/a/71388839/8535752 + * @returns Returns true if running on the client, false otherwise + */ +// eslint-disable-next-line import/prefer-default-export +export const isClient = () => + typeof process === 'undefined' || !process || process.type === 'renderer'; diff --git a/src/shared/util/PapiUtil.ts b/src/shared/util/PapiUtil.ts new file mode 100644 index 0000000000..46f262f11c --- /dev/null +++ b/src/shared/util/PapiUtil.ts @@ -0,0 +1,211 @@ +// #region Unsubscriber stuff + +/** Function to run to dispose of something. Returns true if successfully unsubscribed */ +export type Unsubscriber = () => boolean; + +/** Object containing both a function to run to dispose of something and a promise that resolves when that thing is done subscribing */ +export type UnsubPromise = { + /** Promise that resolves when done registering */ + promise: Promise; + /** Unsubscriber function that unregisters */ + unsubscriber: Unsubscriber; +}; + +/** + * Returns an Unsubscriber function that combines all the unsubscribers passed in. + * @param unsubscribers all unsubscribers to aggregate into one unsubscriber + * @returns function that unsubscribes from all passed in unsubscribers when run + */ +export const aggregateUnsubscribers = ( + unsubscribers: Unsubscriber[], +): Unsubscriber => { + return () => { + // Run the unsubscriber for each handler + const unsubs = unsubscribers.map((unsubscriber) => unsubscriber()); + + // If any of the unsubscribers resolves to false, we did not succeed + return !unsubs.includes(false); + }; +}; + +/** Function to run to dispose of something that runs asynchronously. The promise resolves to true if successfully unsubscribed */ +export type UnsubscriberAsync = () => Promise; + +/** Object containing both a function to run to dispose of something and a promise that resolves when that thing is done subscribing */ +export type UnsubPromiseAsync = { + /** Promise that resolves when done registering */ + promise: Promise; + /** Unsubscriber function that unregisters */ + unsubscriber: UnsubscriberAsync; +}; + +/** + * Returns an UnsubscriberAsync function that combines all the unsubscribers passed in. + * @param unsubscribers all unsubscribers to aggregate into one unsubscriber + * @returns function that unsubscribes from all passed in unsubscribers when run + */ +export const aggregateUnsubscriberAsyncs = ( + unsubscribers: UnsubscriberAsync[], +): UnsubscriberAsync => { + return async () => { + // Run the unsubscriber for each handler + const unsubPromises = unsubscribers.map((unsubscriber) => unsubscriber()); + + // If any of the unsubscribers resolves to false, we did not succeed + // TODO: make a util function to tune up this Promise.all so they all resolve even if one throws + return !(await Promise.all(unsubPromises)).includes(false); + }; +}; + +/** + * Creates a safe version of a register function that returns an UnsubPromiseAsync. + * This is a challenge because we want to provide an unsubscriber that functions + * even before the UnsubPromise.promise resolves. + * TODO: This isn't quite fully safe yet. See TODO below. Basically, if you run this + * before initializing, the unsubscriber returned may not work if you call it + * immediately, but it will also throw an exception (we can remove this if we + * actually run into this case and it seems to work fine). You should wait to call the unsubscriber later + * @param unsafeRegisterFn function that does some kind of async registration and returns an unsubscriber and a promise that resolves when the registration is finished + * @param isInitialized whether the service associated with this safe unsubPromiseAsync function is initialized + * @param initialize promise that resolves when the service is finished initializing + * @param backupUnregisterFn a backup unsubscriber function that should attempt to unsubscribe whatever the unsafeRegisterFn is subscribing before unsafeRegisterFn finishes subscribing and resolves. Will be overwritten with the actual unsubscriber once the unsafeRegisterFn promise resolves. See TODO above for more info + * @returns safe version of an unsafe function that returns an UnsubPromiseAsync (meaning it will wait to register until the service is initialized) + */ +export const createSafeRegisterFn = , TReturn>( + unsafeRegisterFn: (...args: TParam) => UnsubPromiseAsync, + isInitialized: boolean, + initialize: () => Promise, + backupUnregisterFn?: (...args: TParam) => Promise, +): ((...args: TParam) => UnsubPromiseAsync) => { + return (...args: TParam) => { + // If we're already initialized, run registerRequestHandler almost like normal but with an initialize check in the unsubscriber + if (isInitialized) { + const { promise, unsubscriber: regUnsubscriber } = unsafeRegisterFn( + ...args, + ); + // Use the returned unsubPromise's unsubscriber to make a safe unregisterRequestHandler + return { + promise, + unsubscriber: async () => { + await initialize(); + return regUnsubscriber(); + }, + }; + } + + // Create an object with a stable unsubscriber reference that we can change when we get the real unsubscriber after awaiting intialize + const unsubRef: { unsubscriber: UnsubscriberAsync } = { + unsubscriber: async () => { + // TODO: The unsubscriber we return might not actually do anything meaningful at first (it attempts to call a backup unsubscriber function, which is probably not what we want), so it throws an exception. Refactor this mess so we aren't giving a stunted unsubscriber at first and then subsequently empowering it after initialize is finished + // TODO: Should the unsubscriber await initialize first, or should it just go ahead and run it? Also below + const didUnregister = backupUnregisterFn + ? await backupUnregisterFn(...args) + : false; + throw new Error( + `unsubscribe run from safeRegisterFn before service finished initializing! unsubscribe was${ + didUnregister ? '' : ' not' + } successful.`, + ); + }, + }; + return { + promise: initialize().then(() => { + const newUnsubAndPromise = unsafeRegisterFn(...args); + // Change the returned unsubAndPromise's unsubscriber to be a safe unregisterRequestHandler + unsubRef.unsubscriber = async () => { + // TODO: Should the unsubscriber await initialize first, or should it just go ahead and run it? Also above + await initialize(); + return newUnsubAndPromise.unsubscriber(); + }; + return newUnsubAndPromise.promise; + }), + unsubscriber: () => unsubRef.unsubscriber(), + }; + }; +}; + +// #endregion + +/** + * Type of object passed to a complex request handler that provides information about the request. + * This type is used as the public-facing interface for requests + */ +export type ComplexRequest = { + contents: TParam; +}; + +type ComplexResponseSuccess = { + /** Whether the handler that created this response was successful in handling the request */ + success: true; + /** Content with which to respond to the request. Must be provided unless the response failed or TReturn is undefined */ + contents: TReturn; +}; + +type ComplexResponseFailure = { + /** Whether the handler that created this response was successful in handling the request */ + success: false; + /** + * Content with which to respond to the request. Must be provided unless the response failed or TReturn is undefined + * Removed from failure so we do not change the type of contents for type safety. We could add errorContents one day if we really need it + */ + /* contents?: TReturn; */ + /** Error explaining the problem that is only populated if success is false */ + errorMessage: string; +}; + +/** + * Type of object to create when handling a complex request where you desire to provide additional information beyond the contents of the response + * This type is used as the public-facing interface for responses + */ +export type ComplexResponse = + | ComplexResponseSuccess + | ComplexResponseFailure; + +/** Type of request handler - indicates what type of parameters and what return type the handler has */ +export enum RequestHandlerType { + Args = 'args', + Contents = 'contents', + Complex = 'complex', +} + +/** + * Handler function for a command. Called when a command is executed. + * The function should accept the command's parameters as its parameters. + * The function should return a promise that resolves with the "return" value of the command. + */ +// Any is probably fine because we likely never know or care about the args or return +export type CommandHandler< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TParam extends Array = any[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TReturn = any, +> = (...args: TParam) => Promise; + +/** Prefix on requests that indicates that the request is a command */ +export const CATEGORY_COMMAND = 'command'; + +/** Information about a request that tells us what to do with it */ +export type RequestType = { + /** the general category of request */ + category: string; + /** specific identifier for this type of request */ + directive: string; +}; + +/** + * Create a request message requestType string from a category and a directive + * @param category the general category of request + * @param directive specific idenitifer for this type of request + * @returns full requestType for use in network calls + */ +export const serializeRequestType = ( + category: string, + directive: string, +): string => `${category}:${directive}`; + +/** Split a request message requestType string into its parts */ +export const deserializeRequestType = (requestType: string): RequestType => { + // TODO: fix this to split on one colon and leave all the rest. Right now, directive is anything between the first and second colons, which is probably bad + const [category, directive] = requestType.split(':', 1); + return { category, directive }; +}; diff --git a/src/shared/util/Util.ts b/src/shared/util/Util.ts new file mode 100644 index 0000000000..f3413fe11d --- /dev/null +++ b/src/shared/util/Util.ts @@ -0,0 +1,62 @@ +// Thanks to blubberdiblub at https://stackoverflow.com/a/68141099/217579 +export function newGuid(): string { + return '00-0-4-1-000'.replace(/[^-]/g, (s) => + // @ts-expect-error ts(2363) this works fine + // eslint-disable-next-line no-bitwise + (((Math.random() + ~~s) * 0x10000) >> s).toString(16).padStart(4, '0'), + ); +} + +// thanks to DRAX at https://stackoverflow.com/a/9436948 +/** + * Determine whether the object is a string + * @param o object to determine if it is a string + * @returns true if the object is a string; false otherwise + */ +export function isString(o: unknown) { + return typeof o === 'string' || o instanceof String; +} + +// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript +type ErrorWithMessage = { + message: string; +}; +// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript +function isErrorWithMessage(error: unknown): error is ErrorWithMessage { + return ( + typeof error === 'object' && + error !== null && + 'message' in error && + typeof (error as Record).message === 'string' + ); +} +// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript +/** + * Function to get an error from the object (useful for getting an error in a catch block) + * @param error error object whose message to get + * @returns message of the error - if object has message, returns message. Otherwise tries to stringify + */ +function toErrorWithMessage(maybeError: unknown): ErrorWithMessage { + if (isErrorWithMessage(maybeError)) return maybeError; + + try { + return new Error(JSON.stringify(maybeError)); + } catch { + // fallback in case there's an error stringifying the maybeError + // like with circular references for example. + return new Error(String(maybeError)); + } +} + +// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript +/** + * Function to get an error message from the object (useful for getting error message in a catch block) + * @param error error object whose message to get + * @returns message of the error - if object has message, returns message. Otherwise tries to stringify + * @example + * try {...} + * catch (e) { console.log(getErrorMessage(e)) } + */ +export function getErrorMessage(error: unknown) { + return toErrorWithMessage(error).message; +} diff --git a/tsconfig.json b/tsconfig.json index 05dfdd8c11..3e9570fc0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,17 @@ "strict": true, "sourceMap": true, "baseUrl": "./src", + "paths": { + "@renderer/*": ["./renderer/*"], + "@shared/*": ["./shared/*"], + "@main/*": ["./main/*"], + "@node_modules/*": ["../node_modules/*"], + "@assets/*": ["../assets/*"] + }, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true,