From 19312727bcd562a245412734ba67a4cf8b354c3c Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 13 Jan 2022 15:07:24 -0800 Subject: [PATCH 01/36] minor configuration updates add: * LICENSE * crx and xpi build scripts * Firefox ID in manifest - for installing an unsigned xpi in FF Developer edition fix: * webpack config to support building on Windows change: * name of output directory from 'dist' to 'PrivacyPass' * npm 'clean' script to include: - typescript output 'lib' directory - crx and xpi files generated by new build scripts * npm 'sjcl' script to explicitly call perl, rather than depending upon the shell to parse the shebang line --- .bin/chromium/pack_crx3_with_chrome.bat | 16 +++ .bin/chromium/pack_crx3_with_chrome.sh | 28 +++++ .bin/chromium/pack_crx3_with_openssl.sh | 105 ++++++++++++++++++ ...igned-extensions-permanently-to-firefox.md | 62 +++++++++++ .bin/firefox/pack_xpi_with_7zip.bat | 15 +++ .bin/firefox/pack_xpi_with_zip.sh | 20 ++++ .gitattributes | 5 + .gitignore | 11 +- LICENSE.txt | 26 +++++ README.md | 6 +- package.json | 4 +- public/manifest.json | 5 + webpack.config.js | 12 +- 13 files changed, 305 insertions(+), 10 deletions(-) create mode 100755 .bin/chromium/pack_crx3_with_chrome.bat create mode 100755 .bin/chromium/pack_crx3_with_chrome.sh create mode 100755 .bin/chromium/pack_crx3_with_openssl.sh create mode 100644 .bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md create mode 100755 .bin/firefox/pack_xpi_with_7zip.bat create mode 100755 .bin/firefox/pack_xpi_with_zip.sh create mode 100644 .gitattributes create mode 100644 LICENSE.txt diff --git a/.bin/chromium/pack_crx3_with_chrome.bat b/.bin/chromium/pack_crx3_with_chrome.bat new file mode 100755 index 00000000..0c7195dd --- /dev/null +++ b/.bin/chromium/pack_crx3_with_chrome.bat @@ -0,0 +1,16 @@ +@echo off + +set CHROME_HOME=C:\PortableApps\Google Chrome\97.0.4692.71\App\Chrome-bin +set CHROME_HOME=C:\PortableApps\SRWare Iron\85.0.4350.0\Iron +set PATH=%CHROME_HOME%;%PATH% + +cd /D "%~dp0..\.." + +set ext_dir="%cd%\PrivacyPass" +set ext_key="%cd%\PrivacyPass.pem" + +if exist %ext_key% ( + chrome --pack-extension=%ext_dir% --pack-extension-key=%ext_key% +) else ( + chrome --pack-extension=%ext_dir% +) diff --git a/.bin/chromium/pack_crx3_with_chrome.sh b/.bin/chromium/pack_crx3_with_chrome.sh new file mode 100755 index 00000000..f5b082ba --- /dev/null +++ b/.bin/chromium/pack_crx3_with_chrome.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# ------------------------------------------------------------------------------ +# configuration + +CHROME_HOME='/c/PortableApps/Google Chrome/97.0.4692.71/App/Chrome-bin' +CHROME_HOME='/c/PortableApps/SRWare Iron/85.0.4350.0/Iron' +PATH="${CHROME_HOME}:${PATH}" + +# ------------------------------------------------------------------------------ +# bootstrap + +function main { + cd "${DIR}/../.." + cwd=$(realpath .) + ext_dir="${cwd}/PrivacyPass" + ext_key="${cwd}/PrivacyPass.pem" + + if [ -f "$ext_key" ];then + chrome "--pack-extension=${ext_dir}" "--pack-extension-key=${ext_key}" + else + chrome "--pack-extension=${ext_dir}" + fi +} + +main diff --git a/.bin/chromium/pack_crx3_with_openssl.sh b/.bin/chromium/pack_crx3_with_openssl.sh new file mode 100755 index 00000000..a5abc7aa --- /dev/null +++ b/.bin/chromium/pack_crx3_with_openssl.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# ------------------------------------------------------------------------------ +# configuration + +OPENSSL_HOME='/c/PortableApps/OpenSSL/1.1.0' +PATH="${OPENSSL_HOME}:${PATH}" + +# ------------------------------------------------------------------------------ +# Source: https://stackoverflow.com/a/18709204 +# Purpose: Pack a Chromium extension directory into crx format +# notes: all temporary files are created in the cwd. +# the final crx is created adjacent to the input extension directory. + +function pack_crx3 { + if test $# -ne 2; then + echo "Usage: crxmake.sh " + exit 1 + fi + + ext_dir=$1 + ext_key=$2 + crx="${ext_dir}.crx" + name=$(basename "$ext_dir") + pub="${name}.pub" + sig="${name}.sig" + zip="${name}.zip" + tosign="${name}.presig" + binary_crx_id="${name}.crxid" + + echo "writing '${name}.crx'" + + # preparation: remove previous crx + rm -f "$crx" + + # preparation: remove all previous temporary files in the cwd + rm -f "$pub" "$sig" "$zip" "$tosign" "$binary_crx_id" + + # cleanup: remove all temporary files in the cwd + trap 'rm -f "$pub" "$sig" "$zip" "$tosign" "$binary_crx_id"' EXIT + + # zip up the crx dir + cwd=$(pwd -P) + (cd "$ext_dir" && zip -qr -9 -X "${cwd}/${zip}" .) + + #extract crx id + openssl rsa -in "$ext_key" -pubout -outform der | openssl dgst -sha256 -binary -out "$binary_crx_id" + truncate -s 16 "$binary_crx_id" + + #generate file to sign + ( + # echo "$crmagic_hex $version_hex $header_length $pub_len_hex $sig_len_hex" + printf "CRX3 SignedData" + echo "00 12 00 00 00 0A 10" | xxd -r -p + cat "$binary_crx_id" "$zip" + ) > "$tosign" + + # signature + openssl dgst -sha256 -binary -sign "$ext_key" < "$tosign" > "$sig" + + # public key + openssl rsa -pubout -outform DER < "$ext_key" > "$pub" 2>/dev/null + + crmagic_hex='43 72 32 34' # Cr24 + version_hex='03 00 00 00' # 3 + header_length='45 02 00 00' + header_chunk_1='12 AC 04 0A A6 02' + header_chunk_2='12 80 02' + header_chunk_3='82 F1 04 12 0A 10' + ( + echo "${crmagic_hex} ${version_hex} ${header_length} ${header_chunk_1}" | xxd -r -p + cat "$pub" + echo "$header_chunk_2" | xxd -r -p + cat "$sig" + echo "$header_chunk_3" | xxd -r -p + cat "$binary_crx_id" "$zip" + ) > "$crx" + + echo 'success: crx3 Chrome extension has been packed' +} + +# ------------------------------------------------------------------------------ +# bootstrap + +function main { + cd "${DIR}/../.." + cwd=$(pwd -P) + ext_dir="${cwd}/PrivacyPass" + ext_key="${cwd}/PrivacyPass.pem" + + TMP="${DIR}/temp" + [ -d "$TMP" ] && rm -rf "$TMP" + mkdir "$TMP" + + cd "$TMP" + pack_crx3 "$ext_dir" "$ext_key" + + # cleanup: remove temporary directory + cd "$DIR" + rm -rf "$TMP" +} + +main diff --git a/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md b/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md new file mode 100644 index 00000000..46a5d4bd --- /dev/null +++ b/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md @@ -0,0 +1,62 @@ +- - - - + +# Add-on signing in Firefox + +### What are my options if I want to use an unsigned add-on? + +Firefox [Extended Support Release (ESR)](https://www.mozilla.org/firefox/organizations/), Firefox [Developer Edition](https://www.mozilla.org/firefox/developer/) and [Nightly](https://nightly.mozilla.org/) versions of Firefox will allow you to override the setting to enforce the extension signing requirement, by changing the preference `xpinstall.signatures.required` to __false__ in the [Firefox Configuration Editor](https://support.mozilla.org/en-US/kb/about-config-editor-firefox) (`about:config` page). To override the language pack signing requirement, you would set the preference `extensions.langpacks.signatures.required` to __false__. There are also special unbranded versions of Firefox that allow this override. See the MozillaWiki article, [Add-ons/Extension Signing](https://wiki.mozilla.org/Add-ons/Extension_Signing) for more information. + +> The source of this post can be found: +> * [here](https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox?#w_what-are-my-options-if-i-want-to-use-an-unsigned-add-on-advanced-users) in HTML format + +- - - - + +# Installing unsigned extensions permanently to Firefox + +2020-11-26 + +If you have worked with browser extension on Firefox, you likely go to `about:debugging` for installing the extensions temporary, while useful for development, the extension gets removed once Firefox restarts. + +Sometimes you may need to test how the extension behaves when Firefox starts, or, just want to leave your extension installed without signing it with the Developer Hub. + + +## Summary + +Gladly, there is a simple solution: +1. Update your extension manifest to include custom `browser_specific_settings`. +2. Disable signature checks while installing extensions. +3. Package your extension as a zip file. +4. Install the extension. +5. Enable signature checks while installing extensions. + + +### Step 1 +Update your `manifest.json` to include a new key, the `id` could be any email: + +```json +"browser_specific_settings": { + "gecko": { + "id": "test@gmail.com" + } +} +``` + +### Step 2 +Go to `about:config`, change `xpinstall.signatures.required` to `false`. + +### Step 3 +Simply run `zip -r -FS ../my-extension.zip * --exclude '*.git*'`. + +### Step 4 +Go to `about:addons`, and choose the `Install Add-on from file` option, choose the zip file created in the previous step. + +### Step 5 +Go to `about:config`, change `xpinstall.signatures.required` to `true`. + +That's it, you have installed an unsigned extension permanently. + +> The source of this post can be found: +> * [here](https://wiringbits.net/browser-extensions/2020/11/27/installing-unsigned-extensions-permanently-to-firefox.html) in HTML format +> * [here](https://github.com/wiringbits/wiringbits.github.io/blob/4f08ae14f53df32809420675d36b21deca081401/_posts/2020-11-26-installing-unsigned-extensions-permanently-to-firefox.md) in Markdown format + +- - - - diff --git a/.bin/firefox/pack_xpi_with_7zip.bat b/.bin/firefox/pack_xpi_with_7zip.bat new file mode 100755 index 00000000..6c4f1491 --- /dev/null +++ b/.bin/firefox/pack_xpi_with_7zip.bat @@ -0,0 +1,15 @@ +@echo off + +set ZIP7_HOME=C:\PortableApps\7-Zip\16.02\App\7-Zip64 +set PATH=%ZIP7_HOME%;%PATH% + +cd /D "%~dp0..\.." + +set ext_name=PrivacyPass +set xpi_file="%cd%\%ext_name%.xpi" + +cd "%ext_name%" + +rem :: https://sevenzip.osdn.jp/chm/cmdline/index.htm +rem :: https://sevenzip.osdn.jp/chm/cmdline/commands/add.htm +7z a -tzip %xpi_file% -r . diff --git a/.bin/firefox/pack_xpi_with_zip.sh b/.bin/firefox/pack_xpi_with_zip.sh new file mode 100755 index 00000000..4d0cf02e --- /dev/null +++ b/.bin/firefox/pack_xpi_with_zip.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# ------------------------------------------------------------------------------ +# bootstrap + +function main { + cd "${DIR}/../.." + cwd=$(pwd -P) + ext_name='PrivacyPass' + xpi_file="${cwd}/${ext_name}.xpi" + + cd "$ext_name" + + # https://extensionworkshop.com/documentation/publish/package-your-extension/#package-linux + zip -r -FS "$xpi_file" * +} + +main diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..eaeed682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto + +*.cmd text eol=crlf +*.bat text eol=crlf +*.sh text eol=lf diff --git a/.gitignore b/.gitignore index fa3c6e08..3ce201dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ *.swp -/node_modules -/dist -/lib +node_modules/ +lib/ +PrivacyPass/ +PrivacyPass.pem +PrivacyPass.crx +PrivacyPass.xpi + +.bin/**/temp/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..e07341a9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2017-2020, Privacy Pass Team, Cloudflare, Inc., and other contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 6b9d2a82..0c26b163 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ $ npm ci $ npm run build ``` -After that, the `dist` folder will contain all files required by the extension. +After that, the `PrivacyPass` folder will contain all files required by the extension. ## Development Installation @@ -47,7 +47,7 @@ After that, the `dist` folder will contain all files required by the extension. - Build by following the [Build Instruction](#build-instruction). - Open Firefox and go to `about:debugging#/runtime/this-firefox`. - Click on 'Load Temporary Add-on' button. -- Select `manifest.json` from `dist` folder. +- Select `manifest.json` from `PrivacyPass` folder. - Check extension logo appears in the top-right corner and 0 passes are stored (by clicking on it). - Go to a web page supporting Privacy Pass where internet challenges @@ -68,7 +68,7 @@ After that, the `dist` folder will contain all files required by the extension. - Open Chrome and go to `chrome://extensions`. - Turn on the Developer mode on the top-right corner. - Click on 'Load unpacked' button. -- Select the `dist` folder. +- Select the `PrivacyPass` folder. - Check extension logo appears in the top-right corner and follow the same instruction as in Firefox. (If you cannot see the extension logo, it's probably just not pinned to the toolbar yest) diff --git a/package.json b/package.json index fb4fe06e..242cac9b 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,13 @@ "license": "BSD-3-Clause", "type": "module", "scripts": { - "sjcl": "cd node_modules/sjcl && ./configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js", + "sjcl": "cd node_modules/sjcl && perl configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js", "prebuild": "npm run sjcl", "build": "webpack", "pretest": "npm run sjcl", "test": "node --experimental-vm-modules node_modules/.bin/jest", "lint": "eslint ./src/**/*.{ts,tsx}", - "clean": "rimraf dist" + "clean": "rimraf lib && rimraf PrivacyPass && rimraf PrivacyPass.crx && rimraf PrivacyPass.xpi" }, "dependencies": { "asn1-parser": "^1.1.8", diff --git a/public/manifest.json b/public/manifest.json index 3a5edf12..02025b86 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -24,5 +24,10 @@ "default_icon": "icons/32/grey.png", "default_title": "Privacy Pass", "default_popup": "popup.html" + }, + "browser_specific_settings": { + "gecko": { + "id": "challenge-bypass-extension@privacypass.github.io" + } } } diff --git a/webpack.config.js b/webpack.config.js index 14422530..1a06a254 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,15 @@ import path from 'path'; // import buffer from "buffer"; // import streamBrowserify from "stream-browserify"; -const __dirname = path.dirname(new URL(import.meta.url).pathname); +const __dirname = (() => { + const filepath_uri = import.meta.url; + const prefix = `file:${path.sep === '/' ? '' : path.sep}`; + let fp; + fp = path.normalize(filepath_uri); + fp = (fp.indexOf(prefix) === 0) ? fp.substring(prefix.length, fp.length) : new URL(filepath_uri).pathname; + fp = path.dirname(fp); + return fp; +})(); const tsloader = { test: /\.tsx?$/, @@ -20,7 +28,7 @@ const tsloader = { const common = { output: { - path: path.resolve('dist'), + path: path.resolve('PrivacyPass'), }, context: __dirname, mode: 'production', From a623deef1a841b846afef31f83277de12a9adfe0 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Fri, 14 Jan 2022 03:06:18 -0800 Subject: [PATCH 02/36] work in progress.. --- public/manifest.json | 2 +- src/background/index.ts | 41 +++++++++++++++++--------- src/background/providers/cloudflare.ts | 28 +++++++++--------- src/background/providers/hcaptcha.ts | 22 +++++++++----- src/background/providers/index.ts | 34 ++++++++++----------- src/background/providers/provider.ts | 31 +++++++++++++++++++ src/background/tab.ts | 14 +++++---- 7 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 src/background/providers/provider.ts diff --git a/public/manifest.json b/public/manifest.json index 02025b86..9be6a7e7 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -2,7 +2,7 @@ "name": "Privacy Pass", "manifest_version": 2, "description": "Client support for Privacy Pass anonymous authorization protocol.", - "version": "3.0.0", + "version": "3.1.0", "icons": { "32": "icons/32/gold.png", "48": "icons/48/gold.png", diff --git a/src/background/index.ts b/src/background/index.ts index 46c79005..529640f2 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,8 +1,7 @@ -import { - handleBeforeRequest, - handleBeforeSendHeaders, - handleHeadersReceived, -} from './listeners/webRequestListener'; +import { Providers, EarnedTokenCookie } from './providers'; + +import { Tab } from './tab'; + import { handleActivated, handleCreated, @@ -10,7 +9,17 @@ import { handleReplaced, } from './listeners/tabListener'; -import { Tab } from './tab'; +import { + handleBeforeRequest, + handleBeforeSendHeaders, + handleHeadersReceived, +} from './listeners/webRequestListener'; + +import * as voprf from './voprf'; + +/* Initialize shared modules */ + +voprf.initECSettings(voprf.defaultECSettings); /* Listeners for navigator */ @@ -87,14 +96,18 @@ chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { } }); -// TODO It's better to move this to the provider class. Let's figure out how to do it later. -// Removes cookies for captcha.website to enable getting more tokens in the future. chrome.cookies.onChanged.addListener((changeInfo) => { - if ( - !changeInfo.removed && - changeInfo.cookie.domain === '.captcha.website' && - changeInfo.cookie.name === 'cf_clearance' - ) { - chrome.cookies.remove({ url: 'https://captcha.website', name: 'cf_clearance' }); + if (!changeInfo.removed && Array.isArray(Providers) && Providers.length) { + for (const provider of Providers) { + const cookie: EarnedTokenCookie | void = provider.EARNED_TOKEN_COOKIE; + if (!cookie) continue; + + if ( + changeInfo.cookie.domain === cookie.domain && + changeInfo.cookie.name === cookie.name + ) { + chrome.cookies.remove({ url: cookie.url, name: cookie.name }); + } + } } }); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 2f9f1fe1..e64ecb68 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -1,6 +1,6 @@ import * as voprf from '../voprf'; -import { Callbacks, Provider } from '.'; +import { Provider, EarnedTokenCookie, Callbacks } from './provider'; import { Storage } from '../storage'; import Token from '../token'; import axios from 'axios'; @@ -24,27 +24,31 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExf0AftemLr0YSz5odoj3eJv6SkOF VcH7NNb2xwdEz6Pxm44tvovEl/E+si8hdIDVg1Ys+cbaWwP0jYJW3ygv+Q== -----END PUBLIC KEY-----`; -const TOKEN_STORE_KEY = 'tokens'; +const TOKEN_STORE_KEY: string = 'tokens'; interface RedeemInfo { requestId: string; token: Token; } -export class CloudflareProvider implements Provider { +export class CloudflareProvider extends Provider { static readonly ID: number = 1; - private callbacks: Callbacks; - private storage: Storage; + static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie = { + url: `https://${DEFAULT_ISSUING_HOSTNAME}`, + domain: `.${DEFAULT_ISSUING_HOSTNAME}`, + name: 'cf_clearance' + }; + + private callbacks: Callbacks; + private storage: Storage; private redeemInfo: RedeemInfo | null; constructor(storage: Storage, callbacks: Callbacks) { - // TODO This changes the global state in the crypto module, which can be a side effect outside of this object. - // It's better if we can refactor the crypto module to be in object-oriented concept. - voprf.initECSettings(voprf.defaultECSettings); + super(storage, callbacks); - this.callbacks = callbacks; - this.storage = storage; + this.callbacks = callbacks; + this.storage = storage; this.redeemInfo = null; } @@ -65,10 +69,6 @@ export class CloudflareProvider implements Provider { ); } - getID(): number { - return CloudflareProvider.ID; - } - private async getCommitment(version: string): Promise<{ G: string; H: string }> { const keyPrefix = 'commitment-'; const cached = this.storage.getItem(`${keyPrefix}${version}`); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index ba9d61c6..7a5f7b2e 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -1,15 +1,23 @@ -import { Callbacks, Provider } from '.'; +import { Provider, EarnedTokenCookie, Callbacks } from './provider'; +import { Storage } from '../storage'; -export class HcaptchaProvider implements Provider { +export class HcaptchaProvider extends Provider { static readonly ID: number = 2; + + static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie = { + url: 'https://www.hcaptcha.com/privacy-pass', + domain: '.hcaptcha.com', + name: 'hc_clearance' + }; + private callbacks: Callbacks; +// private storage: Storage; - constructor(callbacks: Callbacks) { - this.callbacks = callbacks; - } + constructor(storage: Storage, callbacks: Callbacks) { + super(storage, callbacks); - getID(): number { - return HcaptchaProvider.ID; + this.callbacks = callbacks; +// this.storage = storage; } private getBadgeText(): string { diff --git a/src/background/providers/index.ts b/src/background/providers/index.ts index a133ae97..8e6730d8 100644 --- a/src/background/providers/index.ts +++ b/src/background/providers/index.ts @@ -1,22 +1,20 @@ -export { CloudflareProvider } from './cloudflare'; -export { HcaptchaProvider } from './hcaptcha'; +import { Callbacks, EarnedTokenCookie, Provider } from './provider'; -export interface Provider { - getID(): number; - forceUpdateIcon(): void; - handleBeforeRequest( - details: chrome.webRequest.WebRequestBodyDetails, - ): chrome.webRequest.BlockingResponse | void; - handleHeadersReceived( - details: chrome.webRequest.WebResponseHeadersDetails, - ): chrome.webRequest.BlockingResponse | void; - handleBeforeSendHeaders( - details: chrome.webRequest.WebRequestHeadersDetails, - ): chrome.webRequest.BlockingResponse | void; - handleActivated(): void; +import { CloudflareProvider } from './cloudflare'; +import { HcaptchaProvider } from './hcaptcha'; + +export type { + Callbacks, + EarnedTokenCookie } -export interface Callbacks { - updateIcon(text: string): void; - navigateUrl(url: string): void; +export { + Provider, + CloudflareProvider, + HcaptchaProvider } + +export const Providers: (typeof Provider)[] = [ + CloudflareProvider, + HcaptchaProvider +] diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts new file mode 100644 index 00000000..4a82949b --- /dev/null +++ b/src/background/providers/provider.ts @@ -0,0 +1,31 @@ +import { Storage } from '../storage'; + +export interface Callbacks { + updateIcon(text: string): void; + navigateUrl(url: string): void; +} + +export interface EarnedTokenCookie { + url: string; + domain: string; + name: string; +} + +export abstract class Provider { + static readonly ID: number; + static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie | void; + + constructor(_storage: Storage, _callbacks: Callbacks){} + + abstract forceUpdateIcon(): void; + abstract handleActivated(): void; + abstract handleBeforeRequest( + details: chrome.webRequest.WebRequestBodyDetails, + ): chrome.webRequest.BlockingResponse | void; + abstract handleHeadersReceived( + details: chrome.webRequest.WebResponseHeadersDetails, + ): chrome.webRequest.BlockingResponse | void; + abstract handleBeforeSendHeaders( + details: chrome.webRequest.WebRequestHeadersDetails, + ): chrome.webRequest.BlockingResponse | void; +} diff --git a/src/background/tab.ts b/src/background/tab.ts index bcafb690..05302b63 100644 --- a/src/background/tab.ts +++ b/src/background/tab.ts @@ -1,4 +1,4 @@ -import { CloudflareProvider, HcaptchaProvider, Provider } from './providers'; +import { Provider, CloudflareProvider, HcaptchaProvider } from './providers'; import { LocalStorage } from './storage'; // Header from server to indicate that Privacy Pass is supported. @@ -92,8 +92,12 @@ export class Tab { return; } const [providerId] = details.responseHeaders - .filter((header) => header.name.toLowerCase() === CHL_BYPASS_SUPPORT) - .map((header) => header.value !== undefined && +header.value); + .filter((header) => (header.name.toLowerCase() === CHL_BYPASS_SUPPORT) && header.value) + .map((header) => header.value ? parseInt(header.value, 10) : null); + + if ((typeof providerId !== 'number') || isNaN(providerId)) { + return + } if (details.type === 'main_frame') { // The page in the tab is changed, so the context should change. @@ -103,7 +107,7 @@ export class Tab { // Cloudflare has higher precedence than Hcaptcha. if (providerId === CloudflareProvider.ID && !(this.context instanceof CloudflareProvider)) { - const context = new CloudflareProvider(new LocalStorage('cf'), { + const context = new CloudflareProvider(new LocalStorage('id-' + CloudflareProvider.ID), { updateIcon: this.updateIcon, navigateUrl: this.navigateUrl, }); @@ -115,7 +119,7 @@ export class Tab { !(this.context instanceof CloudflareProvider) && !(this.context instanceof HcaptchaProvider) ) { - this.context = new HcaptchaProvider({ + this.context = new HcaptchaProvider(new LocalStorage('id-' + HcaptchaProvider.ID), { updateIcon: this.updateIcon, navigateUrl: this.navigateUrl, }); From 59e01e2ba685f74bb72dd23936465d919876be2d Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Fri, 14 Jan 2022 03:52:29 -0800 Subject: [PATCH 03/36] work in progress.. --- src/background/index.ts | 22 +++++---------------- src/background/listeners/cookiesListener.ts | 17 ++++++++++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 src/background/listeners/cookiesListener.ts diff --git a/src/background/index.ts b/src/background/index.ts index 529640f2..e7a7f3ae 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,5 +1,3 @@ -import { Providers, EarnedTokenCookie } from './providers'; - import { Tab } from './tab'; import { @@ -15,6 +13,10 @@ import { handleHeadersReceived, } from './listeners/webRequestListener'; +import { + handleChangedCookies, +} from './listeners/cookiesListener'; + import * as voprf from './voprf'; /* Initialize shared modules */ @@ -96,18 +98,4 @@ chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { } }); -chrome.cookies.onChanged.addListener((changeInfo) => { - if (!changeInfo.removed && Array.isArray(Providers) && Providers.length) { - for (const provider of Providers) { - const cookie: EarnedTokenCookie | void = provider.EARNED_TOKEN_COOKIE; - if (!cookie) continue; - - if ( - changeInfo.cookie.domain === cookie.domain && - changeInfo.cookie.name === cookie.name - ) { - chrome.cookies.remove({ url: cookie.url, name: cookie.name }); - } - } - } -}); +chrome.cookies.onChanged.addListener(handleChangedCookies); diff --git a/src/background/listeners/cookiesListener.ts b/src/background/listeners/cookiesListener.ts new file mode 100644 index 00000000..e9035143 --- /dev/null +++ b/src/background/listeners/cookiesListener.ts @@ -0,0 +1,17 @@ +import { Providers, EarnedTokenCookie } from '../providers'; + +export function handleChangedCookies(changeInfo: any): void { + if (!changeInfo.removed && Array.isArray(Providers) && Providers.length) { + for (const provider of Providers) { + const cookie: EarnedTokenCookie | void = provider.EARNED_TOKEN_COOKIE; + if (!cookie) continue; + + if ( + changeInfo.cookie.domain === cookie.domain && + changeInfo.cookie.name === cookie.name + ) { + chrome.cookies.remove({ url: cookie.url, name: cookie.name }); + } + } + } +} From c9b0c4009edc5b5005f3c9163a964f1418dc460f Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Fri, 14 Jan 2022 12:56:13 -0800 Subject: [PATCH 04/36] work in progress.. use chrome.i18n to internationalize strings in UI --- public/_locales/en/messages.json | 34 +++++++++++++++++++ public/manifest.json | 7 ++-- src/background/providers/cloudflare.ts | 2 +- src/popup/components/ClearButton/index.tsx | 2 +- .../components/CloudflareButton/index.tsx | 2 +- src/popup/components/GithubButton/index.tsx | 2 +- src/popup/components/HcaptchaButton/index.tsx | 2 +- src/popup/components/Header/index.tsx | 4 +-- src/popup/components/PassButton/index.tsx | 2 +- webpack.config.js | 2 +- 10 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 public/_locales/en/messages.json diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json new file mode 100644 index 00000000..bed1f1de --- /dev/null +++ b/public/_locales/en/messages.json @@ -0,0 +1,34 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "name of extension included by 'manifest.json', and displayed by '@popup/components/Header'." + }, + "appDescription": { + "message": "Client support for Privacy Pass anonymous authorization protocol.", + "description": "description of extension included by 'manifest.json'." + }, + "labelAppVersion": { + "message": "Version", + "description": "label of current extension version displayed by '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "name of Cloudflare provider displayed by '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "name of hCaptcha provider displayed by '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Get more passes!", + "description": "mouseover text displayed by '@popup/components/PassButton'." + }, + "ctaClearAllPasses": { + "message": "Clear All Passes", + "description": "displayed by '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "View on Github", + "description": "displayed by '@popup/components/GithubButton'." + } +} diff --git a/public/manifest.json b/public/manifest.json index 9be6a7e7..b503d6d9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,8 +1,9 @@ { - "name": "Privacy Pass", - "manifest_version": 2, - "description": "Client support for Privacy Pass anonymous authorization protocol.", + "name": "__MSG_appName__", + "description": "__MSG_appDescription__", "version": "3.1.0", + "manifest_version": 2, + "default_locale": "en", "icons": { "32": "icons/32/gold.png", "48": "icons/48/gold.png", diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index e64ecb68..dc623fc9 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -291,7 +291,7 @@ export class CloudflareProvider extends Provider { return ( header.name.toLowerCase() === CHL_BYPASS_SUPPORT && header.value !== undefined && - +header.value === CloudflareProvider.ID + parseInt(header.value, 10) === CloudflareProvider.ID ); }); if (!hasSupportHeader) { diff --git a/src/popup/components/ClearButton/index.tsx b/src/popup/components/ClearButton/index.tsx index d38d6e1c..286cf895 100644 --- a/src/popup/components/ClearButton/index.tsx +++ b/src/popup/components/ClearButton/index.tsx @@ -9,5 +9,5 @@ export function ClearButton(): JSX.Element { const clearPasses = () => { dispatch({ type: 'CLEAR_TOKENS' }); }; - return ; + return ; } diff --git a/src/popup/components/CloudflareButton/index.tsx b/src/popup/components/CloudflareButton/index.tsx index 8503dae8..3a5b0c85 100644 --- a/src/popup/components/CloudflareButton/index.tsx +++ b/src/popup/components/CloudflareButton/index.tsx @@ -17,7 +17,7 @@ export function CloudflareButton(): JSX.Element { return ( - Cloudflare + {chrome.i18n.getMessage('providerNameCloudflare')} ); } diff --git a/src/popup/components/GithubButton/index.tsx b/src/popup/components/GithubButton/index.tsx index 3d7d8160..614dbf03 100644 --- a/src/popup/components/GithubButton/index.tsx +++ b/src/popup/components/GithubButton/index.tsx @@ -7,5 +7,5 @@ export function GithubButton(): JSX.Element { chrome.tabs.create({ url: 'https://github.com/privacypass/challenge-bypass-extension' }); }; - return ; + return ; } diff --git a/src/popup/components/HcaptchaButton/index.tsx b/src/popup/components/HcaptchaButton/index.tsx index 4c752cc4..f1879fd8 100644 --- a/src/popup/components/HcaptchaButton/index.tsx +++ b/src/popup/components/HcaptchaButton/index.tsx @@ -9,7 +9,7 @@ export function HcaptchaButton(): JSX.Element { return ( - hCaptcha + {chrome.i18n.getMessage('providerNameHcaptcha')} ); } diff --git a/src/popup/components/Header/index.tsx b/src/popup/components/Header/index.tsx index 04dee52b..1107ebd9 100644 --- a/src/popup/components/Header/index.tsx +++ b/src/popup/components/Header/index.tsx @@ -8,8 +8,8 @@ export function Header(): JSX.Element {
-
Privacy Pass
-
Version {packageJson.version}
+
{chrome.i18n.getMessage('appName')}
+
{chrome.i18n.getMessage('labelAppVersion')} {packageJson.version}
); diff --git a/src/popup/components/PassButton/index.tsx b/src/popup/components/PassButton/index.tsx index e56d6b7a..178f963a 100644 --- a/src/popup/components/PassButton/index.tsx +++ b/src/popup/components/PassButton/index.tsx @@ -13,7 +13,7 @@ export function PassButton(props: Props): JSX.Element { setMouseover(false); } - const element = mouseover ? 'Get more passes!' : props.children; + const element = mouseover ? chrome.i18n.getMessage('ctaGetMorePasses') : props.children; return (
Date: Fri, 14 Jan 2022 17:10:35 -0800 Subject: [PATCH 05/36] work in progress.. * update message passing between userscript and background page * update redux store used by popup summary: ======== * messages are currently only passed in one direction: - from userscript to background page (w/ response to callback) - messages: 1. {clear: true} - removes all passes for all providers from local storage - no response 2. {tokensCount: true, providerID: number} - providerID is: * optional; if not included, then the response includes all providers * the same integer that identifies each unique provider 1 == Cloudflare 2 == hCaptcha etc.. - response: {[key: string]: number} * each key is a `${providerID}` * each value is a count of the number of passes in wallet - for example: {"1": 30, "2": 5} * the data structure used by the redux store to hold state is identical to the response from the background page to messages that request 'tokensCount' --- src/background/index.ts | 36 +++++++-------- src/background/listeners/messageListener.ts | 44 +++++++++++++++++++ src/background/providers/cloudflare.ts | 6 +-- src/background/providers/index.ts | 6 +-- src/background/providers/provider.ts | 2 + src/background/storage.ts | 16 +++++-- src/background/tab.ts | 6 +-- .../components/CloudflareButton/index.tsx | 13 +++--- src/popup/index.tsx | 11 ++--- src/popup/store.ts | 11 ++--- 10 files changed, 99 insertions(+), 52 deletions(-) create mode 100644 src/background/listeners/messageListener.ts diff --git a/src/background/index.ts b/src/background/index.ts index e7a7f3ae..5f8dfbe0 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -17,13 +17,17 @@ import { handleChangedCookies, } from './listeners/cookiesListener'; +import { + handleReceivedMessage, +} from './listeners/messageListener'; + import * as voprf from './voprf'; /* Initialize shared modules */ voprf.initECSettings(voprf.defaultECSettings); -/* Listeners for navigator */ +/* Local state */ declare global { interface Window { @@ -35,6 +39,15 @@ declare global { window.ACTIVE_TAB_ID = chrome.tabs.TAB_ID_NONE; window.TABS = new Map(); +/* Access to local state */ + +export function forceUpdateIcon(): void { + const activeTab = window.TABS.get(window.ACTIVE_TAB_ID); + if (activeTab !== undefined) { + activeTab.forceUpdateIcon(); + } +} + /* Listeners for navigator */ chrome.tabs.onActivated.addListener(handleActivated); @@ -79,23 +92,6 @@ chrome.webRequest.onHeadersReceived.addListener(handleHeadersReceived, { urls: [ 'blocking', ]); -// TODO Using Message passing is dirty. It's better to use chrome.storage for sharing -// common data between the popup and the background script. -chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => { - if (request.clear === true) { - window.localStorage.clear(); - - // Update the browser action icon after clearing the tokens. - const activeTab = window.TABS.get(window.ACTIVE_TAB_ID); - if (activeTab !== undefined) { - activeTab.forceUpdateIcon(); - } - return; - } - - if (request.key !== undefined && typeof request.key === 'string') { - sendResponse(window.localStorage.getItem(request.key)); - } -}); - chrome.cookies.onChanged.addListener(handleChangedCookies); + +chrome.runtime.onMessage.addListener(handleReceivedMessage); diff --git a/src/background/listeners/messageListener.ts b/src/background/listeners/messageListener.ts new file mode 100644 index 00000000..3d10fb7c --- /dev/null +++ b/src/background/listeners/messageListener.ts @@ -0,0 +1,44 @@ +import { forceUpdateIcon } from '..' +import { Providers, Provider } from '../providers'; +import { LocalStorage, generatePrefixFromID, clearAllPasses } from '../storage' + +export function handleReceivedMessage(request: any, _sender: chrome.runtime.MessageSender, sendResponse: Function): void { + + // ------------------------------------------------------------------------- + if (request.clear === true) { + clearAllPasses(); + + // Update the browser action icon after clearing the tokens. + forceUpdateIcon(); + + return; + } + + // ------------------------------------------------------------------------- + if (request.tokensCount === true) { + const response: {[key: string]: number} = {}; + + for (const provider of Providers) { + if ((typeof request.providerID !== 'number') || (request.providerID === provider.ID)) { + const storage = new LocalStorage( + generatePrefixFromID(provider.ID) + ); + + const tokensJSON: string | null = storage.getItem(Provider.TOKEN_STORE_KEY); + if (tokensJSON === null) continue; + + try { + const tokensArray: string[] = JSON.parse(tokensJSON); + + response[`${provider.ID}`] = Number(tokensArray.length); + } + catch (error: any) {} + } + } + + sendResponse(response); + return; + } + + // ------------------------------------------------------------------------- +} diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index dc623fc9..0866ae10 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -24,8 +24,6 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExf0AftemLr0YSz5odoj3eJv6SkOF VcH7NNb2xwdEz6Pxm44tvovEl/E+si8hdIDVg1Ys+cbaWwP0jYJW3ygv+Q== -----END PUBLIC KEY-----`; -const TOKEN_STORE_KEY: string = 'tokens'; - interface RedeemInfo { requestId: string; token: Token; @@ -53,7 +51,7 @@ export class CloudflareProvider extends Provider { } private getStoredTokens(): Token[] { - const stored = this.storage.getItem(TOKEN_STORE_KEY); + const stored = this.storage.getItem(Provider.TOKEN_STORE_KEY); if (stored === null) { return []; } @@ -64,7 +62,7 @@ export class CloudflareProvider extends Provider { private setStoredTokens(tokens: Token[]) { this.storage.setItem( - TOKEN_STORE_KEY, + Provider.TOKEN_STORE_KEY, JSON.stringify(tokens.map((token) => token.toString())), ); } diff --git a/src/background/providers/index.ts b/src/background/providers/index.ts index 8e6730d8..ec606072 100644 --- a/src/background/providers/index.ts +++ b/src/background/providers/index.ts @@ -5,16 +5,16 @@ import { HcaptchaProvider } from './hcaptcha'; export type { Callbacks, - EarnedTokenCookie + EarnedTokenCookie, } export { Provider, CloudflareProvider, - HcaptchaProvider + HcaptchaProvider, } export const Providers: (typeof Provider)[] = [ CloudflareProvider, - HcaptchaProvider + HcaptchaProvider, ] diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts index 4a82949b..c38ec6d7 100644 --- a/src/background/providers/provider.ts +++ b/src/background/providers/provider.ts @@ -12,6 +12,8 @@ export interface EarnedTokenCookie { } export abstract class Provider { + static readonly TOKEN_STORE_KEY: string = 'tokens'; + static readonly ID: number; static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie | void; diff --git a/src/background/storage.ts b/src/background/storage.ts index b59eaa28..0e455bed 100644 --- a/src/background/storage.ts +++ b/src/background/storage.ts @@ -1,4 +1,9 @@ -export class LocalStorage { +export interface Storage { + getItem(key: string): string | null; + setItem(key: string, value: string): void; +} + +export class LocalStorage implements Storage { private prefix: string; constructor(prefix: string) { @@ -14,7 +19,10 @@ export class LocalStorage { } } -export interface Storage { - getItem(key: string): string | null; - setItem(key: string, value: string): void; +export function generatePrefixFromID(id: number): string { + return 'id-' + id; +} + +export function clearAllPasses(): void { + window.localStorage.clear(); } diff --git a/src/background/tab.ts b/src/background/tab.ts index 05302b63..9e053589 100644 --- a/src/background/tab.ts +++ b/src/background/tab.ts @@ -1,5 +1,5 @@ import { Provider, CloudflareProvider, HcaptchaProvider } from './providers'; -import { LocalStorage } from './storage'; +import { LocalStorage, generatePrefixFromID } from './storage'; // Header from server to indicate that Privacy Pass is supported. const CHL_BYPASS_SUPPORT = 'cf-chl-bypass'; @@ -107,7 +107,7 @@ export class Tab { // Cloudflare has higher precedence than Hcaptcha. if (providerId === CloudflareProvider.ID && !(this.context instanceof CloudflareProvider)) { - const context = new CloudflareProvider(new LocalStorage('id-' + CloudflareProvider.ID), { + const context = new CloudflareProvider(new LocalStorage(generatePrefixFromID(CloudflareProvider.ID)), { updateIcon: this.updateIcon, navigateUrl: this.navigateUrl, }); @@ -119,7 +119,7 @@ export class Tab { !(this.context instanceof CloudflareProvider) && !(this.context instanceof HcaptchaProvider) ) { - this.context = new HcaptchaProvider(new LocalStorage('id-' + HcaptchaProvider.ID), { + this.context = new HcaptchaProvider(new LocalStorage(generatePrefixFromID(HcaptchaProvider.ID)), { updateIcon: this.updateIcon, navigateUrl: this.navigateUrl, }); diff --git a/src/popup/components/CloudflareButton/index.tsx b/src/popup/components/CloudflareButton/index.tsx index 3a5b0c85..090a2a04 100644 --- a/src/popup/components/CloudflareButton/index.tsx +++ b/src/popup/components/CloudflareButton/index.tsx @@ -3,12 +3,13 @@ import { useSelector } from 'react-redux'; import { PassButton } from '@popup/components/PassButton'; +const providerID: string = '1'; + export function CloudflareButton(): JSX.Element { - const tokens: string[] = useSelector((state: { ['cf-tokens']?: string[] } | undefined) => { - if (state !== undefined && state['cf-tokens'] !== undefined) { - return state['cf-tokens']; - } - return []; + const tokensCount: number = useSelector((state: {[key: string]: number} | void): number => { + return ((state instanceof Object) && (typeof state[providerID] === 'number')) + ? Number(state[providerID]) + : 0; }); const openHomePage = () => { @@ -16,7 +17,7 @@ export function CloudflareButton(): JSX.Element { }; return ( - + {chrome.i18n.getMessage('providerNameCloudflare')} ); diff --git a/src/popup/index.tsx b/src/popup/index.tsx index d79df56b..33a224cc 100644 --- a/src/popup/index.tsx +++ b/src/popup/index.tsx @@ -13,14 +13,11 @@ ReactDOM.render( document.getElementById('root'), ); -// TODO Using Message passing is dirty. It's better to use chrome.storage for sharing -// common data between the popup and the background script. -chrome.runtime.sendMessage({ key: 'cf-tokens' }, (response) => { - if (response !== undefined && typeof response === 'string') { +chrome.runtime.sendMessage({ tokensCount: true }, (response: any) => { + if ((response !== undefined) && (response !== null) && (response instanceof Object)) { store.dispatch({ - type: 'UPDATE_STATE', - key: 'cf-tokens', - value: response, + type: 'UPDATE_STATE', + value: response }); } }); diff --git a/src/popup/store.ts b/src/popup/store.ts index 3a495131..adca5dd7 100644 --- a/src/popup/store.ts +++ b/src/popup/store.ts @@ -2,8 +2,7 @@ import { createStore } from 'redux'; interface UpdateStateAction { type: 'UPDATE_STATE'; - key: string; - value: string; + value: {[key: string]: number}; } interface ClearTokensAction { @@ -13,17 +12,19 @@ interface ClearTokensAction { type Action = UpdateStateAction | ClearTokensAction; const reducer = (state: any | undefined, action: Action) => { + state = (state instanceof Object) ? state : {}; + switch (action.type) { case 'UPDATE_STATE': return { ...state, - [action.key]: JSON.parse(action.value), + ...action.value, }; case 'CLEAR_TOKENS': - // TODO Using Message passing is dirty. It's better to use chrome.storage for sharing - // common data between the popup and the background script. chrome.runtime.sendMessage({ clear: true }); return {}; + default: + return state; } }; From 4b7beb19908b050e8699823c24bd43db941b9020 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 15 Jan 2022 03:07:04 -0800 Subject: [PATCH 06/36] work in progress.. * refactor the 'voprf' crypto module into a reusable class, so each provider can own a distinct instance * add the dependency: 'keccak' - v2 of the extension included a snapshot of this library - v3 removed it, but there is still code that wants to use it * presumably, this branch of code will get called when the "hCaptcha" provider is reimplemented (..coming soon) --- package-lock.json | 25 +- package.json | 11 +- public/manifest.json | 2 +- .../pseudorandom-number-generators/hkdf.js | 44 + .../pseudorandom-number-generators/shake.ts | 6 + src/background/crypto/voprf.d.ts | 77 ++ src/background/crypto/voprf.js | 922 +++++++++++++++++ src/background/crypto/voprf.test.ts | 8 + src/background/index.ts | 6 - src/background/providers/cloudflare.ts | 14 +- src/background/token.test.ts | 10 - src/background/token.ts | 39 +- src/background/voprf.d.ts | 55 -- src/background/voprf.js | 931 ------------------ src/background/voprf.test.ts | 7 - 15 files changed, 1121 insertions(+), 1036 deletions(-) create mode 100644 src/background/crypto/pseudorandom-number-generators/hkdf.js create mode 100644 src/background/crypto/pseudorandom-number-generators/shake.ts create mode 100644 src/background/crypto/voprf.d.ts create mode 100644 src/background/crypto/voprf.js create mode 100644 src/background/crypto/voprf.test.ts delete mode 100644 src/background/voprf.d.ts delete mode 100644 src/background/voprf.js delete mode 100644 src/background/voprf.test.ts diff --git a/package-lock.json b/package-lock.json index c1a870e7..f94676e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "privacy-pass", - "version": "3.0.0", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "privacy-pass", - "version": "3.0.0", + "version": "3.2.0", "license": "BSD-3-Clause", "dependencies": { "asn1-parser": "^1.1.8", "axios": "^0.23.0", "buffer": "^6.0.3", - "keccak": "^3.0.1", + "keccak": "^3.0.2", "qs": "^6.10.1", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -24,6 +24,7 @@ "devDependencies": { "@types/chrome": "^0.0.159", "@types/jest": "^27.0.2", + "@types/keccak": "^3.0.1", "@types/qs": "^6.9.6", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.5", @@ -1270,6 +1271,15 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "16.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", @@ -8302,6 +8312,15 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "16.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", diff --git a/package.json b/package.json index 242cac9b..2e573cd9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "privacy-pass", - "version": "3.0.0", + "version": "3.2.0", + "private": true, "contributors": [ "Suphanat Chunhapanya ", - "Armando Faz " + "Armando Faz ", + "Warren Bank " ], - "main": "index.js", + "browser": "index.js", "license": "BSD-3-Clause", "type": "module", "scripts": { @@ -21,7 +23,7 @@ "asn1-parser": "^1.1.8", "axios": "^0.23.0", "buffer": "^6.0.3", - "keccak": "^3.0.1", + "keccak": "^3.0.2", "qs": "^6.10.1", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -33,6 +35,7 @@ "devDependencies": { "@types/chrome": "^0.0.159", "@types/jest": "^27.0.2", + "@types/keccak": "^3.0.1", "@types/qs": "^6.9.6", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.5", diff --git a/public/manifest.json b/public/manifest.json index b503d6d9..11995ed1 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.1.0", + "version": "3.2.0", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/crypto/pseudorandom-number-generators/hkdf.js b/src/background/crypto/pseudorandom-number-generators/hkdf.js new file mode 100644 index 00000000..5bb3195c --- /dev/null +++ b/src/background/crypto/pseudorandom-number-generators/hkdf.js @@ -0,0 +1,44 @@ +import sjcl from 'sjcl'; + +/** + * hkdf - The HMAC-based Key Derivation Function + * based on https://github.com/mozilla/node-hkdf + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * @param {bitArray} ikm Initial keying material + * @param {integer} length Length of the derived key in bytes + * @param {bitArray} info Key derivation data + * @param {bitArray} salt Salt + * @param {sjcl.hash} hash hash function + * @return {bitArray} + */ +export function evaluateHkdf(ikm, length, info, salt, hash) { + const mac = new sjcl.misc.hmac(salt, hash); + mac.update(ikm); + const prk = mac.digest(); + + const hashLength = Math.ceil(sjcl.bitArray.bitLength(prk) / 8); + const numBlocks = Math.ceil(length / hashLength); + if (numBlocks > 255) { + throw new Error( + `[privacy-pass]: HKDF error, number of proposed iterations too large: ${numBlocks}`, + ); + } + + let prev = sjcl.codec.hex.toBits(''); + let output = ''; + for (let i = 0; i < numBlocks; i++) { + const hmac = new sjcl.misc.hmac(prk, hash); + const input = sjcl.bitArray.concat( + sjcl.bitArray.concat(prev, info), + sjcl.codec.utf8String.toBits(String.fromCharCode(i + 1)), + ); + hmac.update(input); + prev = hmac.digest(); + output += sjcl.codec.hex.fromBits(prev); + } + return sjcl.bitArray.clamp(sjcl.codec.hex.toBits(output), length * 8); +} diff --git a/src/background/crypto/pseudorandom-number-generators/shake.ts b/src/background/crypto/pseudorandom-number-generators/shake.ts new file mode 100644 index 00000000..f033e120 --- /dev/null +++ b/src/background/crypto/pseudorandom-number-generators/shake.ts @@ -0,0 +1,6 @@ +import createKeccakHash, { Shake, ShakeAlgorithm } from 'keccak'; + +export function createShake256(): Shake { + const algorithm: ShakeAlgorithm = 'shake256'; + return createKeccakHash(algorithm); +} diff --git a/src/background/crypto/voprf.d.ts b/src/background/crypto/voprf.d.ts new file mode 100644 index 00000000..1badee72 --- /dev/null +++ b/src/background/crypto/voprf.d.ts @@ -0,0 +1,77 @@ +export type Point = unknown; +export type Bytes = unknown; +export type BigNum = { toString(): string }; + +export type Curve = 'p256'; +export type Hash = 'sha256'; +export type HashMethod = 'increment' | 'swu'; +export type HashLabel = 'H2C-P256-SHA256-SSWU-' | string; + +export interface ECSettings { + curve: any; + hash: Hash; + method: HashMethod; + label?: HashLabel; +} + +export const defaultECSettings: ECSettings; + +export function sec1Encode(point: Point, compressed: boolean): any; +export function sec1EncodeToBase64(point: Point, compressed: boolean): string; +export function getBigNumFromBytes(bytes: any): BigNum; +export function newBigNum(encoded: string): BigNum; +export function getBytesFromString(str: any): any; +export function getBase64FromBytes(bytes: any): any; +export function getBase64FromString(str: any): any; + +export class VOPRF { + private CURVE: any; + private CURVE_H2C_HASH: Hash; + private CURVE_H2C_METHOD: HashMethod; + private CURVE_H2C_LABEL: HashLabel | void; + + constructor(h2cParams: ECSettings | void): void; + getActiveECSettings(): ECSettings; + newRandomPoint(): { data: Bytes; point: Point }; + sec1DecodeFromBase64(encoded: string): Point; + sec1DecodeFromBytes(sec1Bytes: Bytes): Point; + blindPoint(point: Point): { blind: BigNum; point: Point }; + unblindPoint(factor: BigNum, blindedPoint: Point): Point; + verifyConfiguration(publicKey: string, config: any, signature: string): boolean; + verifyProof( + proof: string, + tokens: unknown[], + signatures: { points: Point[]; compressed: boolean }, + commitments: any, + prngName: string, + ): boolean; + deriveKey(N: Point, token: any): any; + createRequestBinding(key: any, data: any): any; + getCurvePoints(signatures: string[]): { + points: Point[]; + compressed: boolean; + }; + + private decompressPoint(bytes: Bytes): Point | null; + private parsePublicKeyfromPEM(pemPublicKey: string): any; + private recomputeComposites( + tokens: unknown[], + signatures: { points: Point[]; compressed: boolean }, + pointG: Point, + pointH: Point, + prngName: string + ): {M: Point; Z: Point}; + private computePRNGScalar( + prng: any, + seed: string, + salt?: any + ): BigNum; + private computeSeed( + chkM: any, + chkZ: Point[], + pointG: Point, + pointH: Point + ): string; + private hashAndInc(seed: any, hash: any, label: any): Point; + private h2Curve(alpha: any, ecSettings: ECSettings): Point; +} diff --git a/src/background/crypto/voprf.js b/src/background/crypto/voprf.js new file mode 100644 index 00000000..1aee9228 --- /dev/null +++ b/src/background/crypto/voprf.js @@ -0,0 +1,922 @@ +/** + * This implements a 2HashDH-based token scheme using the SJCL ecc package. + * + * @author: George Tankersley + * @author: Alex Davidson + */ + +import 'asn1-parser'; + +import sjcl from 'sjcl'; + +import { evaluateHkdf } from './pseudorandom-number-generators/hkdf.js'; +import { createShake256 } from './pseudorandom-number-generators/shake.ts'; + +// ----------------------------------------------------------------------------- +// constant values and static methods + +let PEM; +let ASN1; +if (typeof window !== 'undefined') { + PEM = window.PEM; + ASN1 = window.ASN1; +} + +const BATCH_PROOF_PREFIX = 'batch-proof='; +const MASK = ['0xff', '0x1', '0x3', '0x7', '0xf', '0x1f', '0x3f', '0x7f']; + +const DIGEST_INEQUALITY_ERR = '[privacy-pass]: Recomputed digest does not equal received digest'; +const PARSE_ERR = '[privacy-pass]: Error parsing proof'; + +// 1.2.840.10045.3.1.7 point generation seed +const INC_H2C_LABEL = sjcl.codec.hex.toBits( + '312e322e3834302e31303034352e332e312e3720706f696e742067656e65726174696f6e2073656564', +); +const SSWU_H2C_LABEL = 'H2C-P256-SHA256-SSWU-'; + +export const defaultECSettings = { + curve: 'p256', + hash: 'sha256', + method: 'increment', +}; + +/** + * Multiplies the point P with the scalar k and outputs kP + * @param {sjcl.bn} k scalar + * @param {sjcl.ecc.point} P curve point + * @return {sjcl.ecc.point} + */ +function _scalarMult(k, P) { + const Q = P.mult(k); + return Q; +} + +/** + * Encodes a curve point as bytes in SEC1 uncompressed format + * @param {sjcl.ecc.point} P + * @param {bool} compressed + * @return {sjcl.codec.bytes} + */ +export function sec1Encode(P, compressed) { + let out = []; + if (!compressed) { + const xyBytes = sjcl.codec.bytes.fromBits(P.toBits()); + out = [0x04].concat(xyBytes); + } else { + const xBytes = sjcl.codec.bytes.fromBits(P.x.toBits()); + const y = P.y.normalize(); + const sign = y.limbs[0] & 1 ? 0x03 : 0x02; + out = [sign].concat(xBytes); + } + return out; +} + +/** + * Encodes a curve point into bits for using as input to hash functions etc + * @param {sjcl.ecc.point} point curve point + * @param {bool} compressed flag indicating whether points have been compressed + * @return {sjcl.bitArray} + */ +function sec1EncodeToBits(point, compressed) { + return sjcl.codec.bytes.toBits(sec1Encode(point, compressed)); +} + +/** + * Encodes a point into a base 64 string + * @param {sjcl.ecc.point} point + * @param {bool} compressed + * @return {string} + */ +export function sec1EncodeToBase64(point, compressed) { + return sjcl.codec.base64.fromBits(sec1EncodeToBits(point, compressed)); +} + +/** + * Checks that the signed points from the IssueResponse have consistent + * compression + * @param {Object} compression compression object to be checked for consistency + * @param {bool} setting new setting based on point data + * @return {bool} + */ +function validResponseCompression(compression, setting) { + if (!compression.set) { + compression.on = setting; + compression.set = true; + } else if (compression.on !== setting) { + return false; + } + return true; +} + +// Commitments verification + +/** + * Parse a PEM-encoded signature. + * @param {string} pemSignature - A signature in PEM format. + * @return {sjcl.bitArray} a signature object for sjcl library. + */ +function parseSignaturefromPEM(pemSignature) { + try { + const bytes = PEM.parseBlock(pemSignature); + const json = ASN1.parse(bytes.der); + const r = sjcl.codec.bytes.toBits(json.children[0].value); + const s = sjcl.codec.bytes.toBits(json.children[1].value); + return sjcl.bitArray.concat(r, s); + } catch (e) { + throw new Error('[privacy-pass]: Failed on parsing commitment signature. ' + e.message); + } +} + +/** + * Returns a decoded DLEQ proof as an object that can be verified + * @param {Object} bp batch proof as encoded JSON + * @return {Object} DLEQ proof object + */ +function retrieveProof(bp) { + let dleqProof; + try { + dleqProof = parseDleqProof(atob(bp.P)); + } catch (e) { + console.error(`${PARSE_ERR}: ${e}`); + return; + } + return dleqProof; +} + +/** + * Decode proof string and remove prefix + * @param {string} proof base64-encoded batched DLEQ proof + * @return {Object} JSON batched DLEQ proof + */ +function getMarshaledBatchProof(proof) { + let proofStr = atob(proof); + if (proofStr.indexOf(BATCH_PROOF_PREFIX) === 0) { + proofStr = proofStr.substring(BATCH_PROOF_PREFIX.length); + } + return JSON.parse(proofStr); +} + +/** + * Decode the proof that is sent into an Object + * @param {string} proofStr proof JSON as string + * @return {Object} + */ +function parseDleqProof(proofStr) { + const dleqProofM = JSON.parse(proofStr); + const dleqProof = {}; + dleqProof.R = getBigNumFromB64(dleqProofM.R); + dleqProof.C = getBigNumFromB64(dleqProofM.C); + return dleqProof; +} + +/** + * Return a bignum from a base64-encoded string + * @param {string} b64Str + * @return {sjcl.bn} + */ +function getBigNumFromB64(b64Str) { + const bits = sjcl.codec.base64.toBits(b64Str); + return sjcl.bn.fromBits(bits); +} + +/** + * Return a big number from an array of bytes + * @param {sjcl.codec.bytes} bytes + * @return {sjcl.bn} + */ +export function getBigNumFromBytes(bytes) { + const bits = sjcl.codec.bytes.toBits(bytes); + return sjcl.bn.fromBits(bits); +} + +/** + * Return a big number from hex-encoded string + * @param {string} hex hex-encoded string + * @return {sjcl.bn} + */ +function getBigNumFromHex(hex) { + return sjcl.bn.fromBits(sjcl.codec.hex.toBits(hex)); +} + +const p256Curve = sjcl.ecc.curves.c256; +const precomputedP256 = { + // a=-3, but must be reduced mod p for P256; otherwise, + // inverseMod function loops forever. + A: p256Curve.a.fullReduce(), + B: p256Curve.b, + baseField: p256Curve.field, + c1: p256Curve.b.mul(-1).mul(p256Curve.a.inverseMod(p256Curve.field.modulus)), + c2: p256Curve.field.modulus.sub(1).cnormalize().halveM(), + sqrt: p256Curve.field.modulus.add(1).cnormalize().halveM().halveM(), +}; + +/** + * Converts the number x into a byte array of length n + * @param {Number} x + * @param {Number} n + * @return {sjcl.codec.bytes} + */ +function i2osp(x, n) { + const bytes = []; + for (let i = n - 1; i > -1; i--) { + bytes[i] = x & 0xff; + x = x >> 8; + } + + if (x > 0) { + throw new Error(`[privacy-pass]: number to convert (${x}) is too long for ${n} bytes.`); + } + return bytes; +} + +/** + * hashes bits to the base field (as described in + * draft-irtf-cfrg-hash-to-curve) + * @param {sjcl.bitArray} x bits of element to be translated + * @param {sjcl.ecc.curve} curve elliptic curve + * @param {sjcl.hash} hash hash function object + * @param {string} label context label for domain separation + * @return {int} integer in the base field of curve + */ +function h2Base(x, curve, hash, label) { + const dataLen = sjcl.codec.bytes.fromBits(x).length; + const h = new hash(); + h.update('h2b'); + h.update(label); + h.update(sjcl.codec.bytes.toBits(i2osp(dataLen, 4))); + h.update(x); + const t = h.finalize(); + const y = curve.field.fromBits(t).cnormalize(); + return y; +} + +/** + * hashes bits onto affine curve point using simplified SWU encoding algorithm + * Not constant-time due to conditional check + * @param {sjcl.bitArray} alpha bits to be encoded + * @param {sjcl.ecc.curve} activeCurve elliptic curve + * @param {sjcl.hash} hash hash function for hashing bytes to base field + * @param {String} label + * @return {sjcl.ecc.point} curve point + */ +function simplifiedSWU(alpha, activeCurve, hash, label) { + const params = getCurveParams(activeCurve); + const u = h2Base(alpha, activeCurve, hash, label); + const { X, Y } = computeSWUCoordinates(u, params); + const point = new sjcl.ecc.point(activeCurve, X, Y); + if (!point.isValid()) { + throw new Error(`[privacy-pass]: Generated point is not on curve, X: ${X}, Y: ${Y}`); + } + return point; +} + +/** + * Compute (X,Y) coordinates from integer u + * Operations taken from draft-irtf-cfrg-hash-to-curve.txt at commit + * cea8485220812a5d371deda25b5eca96bd7e6c0e + * @param {sjcl.bn} u integer to map + * @param {Object} params curve parameters + * @return {Object} curve coordinates + */ +function computeSWUCoordinates(u, params) { + const { A, B, baseField, c1, c2, sqrt } = params; + const p = baseField.modulus; + const t1 = u.square().mul(-1); // steps 2-3 + const t2 = t1.square(); // step 4 + let x1 = t2.add(t1); // step 5 + x1 = x1.inverse(); // step 6 + x1 = x1.add(1); // step 7 + x1 = x1.mul(c1); // step 8 + + let gx1 = x1.square().mod(p); // steps 9-12 + gx1 = gx1.add(A); + gx1 = gx1.mul(x1); + gx1 = gx1.add(B); + gx1 = gx1.mod(p); + + const x2 = t1.mul(x1); // step 13 + let gx2 = x2.square().mod(p); // step 14-17 + gx2 = gx2.add(A); + gx2 = gx2.mul(x2); + gx2 = gx2.add(B); + gx2 = gx2.mod(p); + + const e = new baseField(gx1.powermod(c2, p)).equals(new sjcl.bn(1)); // step 18 + const X = cmov(x2, x1, e, baseField); // step 19 + const gx = cmov(gx2, gx1, e, baseField); // step 20 + let y1 = gx.powermod(sqrt, p); // step 21 + // choose the positive (the smallest) root + const r = c2.greaterEquals(y1); + let y2 = y1.mul(-1).mod(p); + const Y = cmov(y2, y1, r, baseField); + return { X: X, Y: Y }; +} + +/** + * Return the parameters for the active curve + * @param {sjcl.ecc.curve} curve elliptic curve + * @return {p;A;B} + */ +function getCurveParams(curve) { + let curveParams; + switch (sjcl.ecc.curveName(curve)) { + case 'c256': + curveParams = precomputedP256; + break; + default: + throw new Error( + '[privacy-pass]: Incompatible curve chosen for H2C: ' + sjcl.ecc.curveName(curve), + ); + } + return curveParams; +} + +/** + * Conditional move selects x or y depending on the bit input. + * @param {sjcl.bn} x is a big number + * @param {sjcl.bn} y is a big number + * @param {boolean} b is a bit + * @param {sjcl.bn} field is the prime field used. + * @return {sjcl.bn} returns x is b=0, otherwise return y. + */ +function cmov(x, y, b, field) { + let z = new field(); + const m = z.radixMask; + const m0 = m & (m + b); + const m1 = m & (m + !b); + x.fullReduce(); + y.fullReduce(); + for (let i = Math.max(x.limbs.length, y.limbs.length) - 1; i >= 0; i--) { + z.limbs.unshift((x.getLimb(i) & m0) ^ (y.getLimb(i) & m1)); + } + return z.mod(field.modulus); +} + +export function newBigNum(s) { + return new sjcl.bn(s); +} + +export function getBytesFromString(str) { + const bits = sjcl.codec.utf8String.toBits(str); + const bytes = sjcl.codec.bytes.fromBits(bits); + return bytes; +} + +export function getBase64FromBytes(bytes) { + const bits = sjcl.codec.bytes.toBits(bytes); + const encoded = sjcl.codec.base64.fromBits(bits); + return encoded; +} + +export function getBase64FromString(str) { + const bits = sjcl.codec.utf8String.toBits(str); + const encoded = sjcl.codec.base64.fromBits(bits); + return encoded; +} + +// ----------------------------------------------------------------------------- +// encapsulated class + +export class VOPRF { + CURVE; + CURVE_H2C_HASH; + CURVE_H2C_METHOD; + CURVE_H2C_LABEL; + + /** + * Sets the curve parameters for the current session based on the contents of + * activeConfig.h2c-params + * @param h2cParams + */ + constructor(h2cParams) { + if (!h2cParams || !(h2cParams instanceof Object)) + h2cParams = defaultECSettings; + + const curveStr = h2cParams.curve; + const hashStr = h2cParams.hash; + const methodStr = h2cParams.method; + + switch (curveStr) { + case 'p256': + if (methodStr != 'swu' && methodStr != 'increment') { + throw new Error( + "[privacy-pass]: Incompatible h2c method: '" + + methodStr + + "', for curve " + + curveStr, + ); + } else if (hashStr != 'sha256') { + throw new Error( + "[privacy-pass]: Incompatible h2c hash: '" + + hashStr + + "', for curve " + + curveStr, + ); + } + this.CURVE = sjcl.ecc.curves.c256; + this.CURVE_H2C_HASH = sjcl.hash.sha256; + this.CURVE_H2C_METHOD = methodStr; + this.CURVE_H2C_LABEL = methodStr === 'increment' ? INC_H2C_LABEL : SSWU_H2C_LABEL; + break; + default: + throw new Error('[privacy-pass]: Incompatible curve chosen: ' + curveStr); + } + } + + /** + * Returns the active configuration for the elliptic curve setting + * @return {Object} Object containing the active curve and h2c configuration + */ + getActiveECSettings() { + return { curve: this.CURVE, hash: this.CURVE_H2C_HASH, method: this.CURVE_H2C_METHOD, label: this.CURVE_H2C_LABEL }; + } + + /** + * Creates a new random point on the curve by sampling random bytes and then + * hashing to the chosen curve. + * @return {sjcl.ecc.point} + */ + newRandomPoint() { + const random = window.crypto.getRandomValues(new Int32Array(8)); + + // Choose hash-to-curve method + const point = this.h2Curve(random, this.getActiveECSettings()); + + let t; + if (point) { + t = { data: sjcl.codec.bytes.fromBits(random), point: point }; + } + return t; + } + + /** + * Decodes a base64-encoded string into a curve point + * @param {string} p a base64-encoded, uncompressed curve point + * @return {sjcl.ecc.point} + */ + sec1DecodeFromBase64(p) { + const sec1Bits = sjcl.codec.base64.toBits(p); + const sec1Bytes = sjcl.codec.bytes.fromBits(sec1Bits); + return this.sec1DecodeFromBytes(sec1Bytes); + } + + /** + * Decodes (SEC1) curve point bytes into a valid curve point + * @param {sjcl.codec.bytes} sec1Bytes bytes of an uncompressed curve point + * @return {sjcl.ecc.point} + */ + sec1DecodeFromBytes(sec1Bytes) { + let P; + switch (sec1Bytes[0]) { + case 0x02: + case 0x03: + P = this.decompressPoint(sec1Bytes); + break; + case 0x04: + P = this.CURVE.fromBits(sjcl.codec.bytes.toBits(sec1Bytes.slice(1))); + break; + default: + throw new Error( + '[privacy-pass]: attempted sec1 point decoding with incorrect tag: ' + sec1Bytes[0], + ); + } + return P; + } + + /** + * Samples a random scalar and uses it to blind the point P + * @param {sjcl.ecc.point} P curve point + * @return {sjcl.ecc.point} + */ + blindPoint(P) { + const bF = sjcl.bn.random(this.CURVE.r, 10); + const bP = _scalarMult(bF, P); + return { point: bP, blind: bF }; + } + + /** + * unblindPoint takes an assumed-to-be blinded point Q and an accompanying + * blinding scalar b, then returns the point (1/b)*Q. + * @param {sjcl.bn} b scalar blinding factor + * @param {sjcl.ecc.point} Q curve point + * @return {sjcl.ecc.point} + */ + unblindPoint(b, Q) { + const binv = b.inverseMod(this.CURVE.r); + return _scalarMult(binv, Q); + } + + /** + * Attempts to decompress a curve point in SEC1 encoded format. Returns null if + * the point is invalid + * @param {sjcl.codec.bytes} bytes bytes of a compressed curve point (SEC1) + * @return {sjcl.ecc.point} may be null if compressed bytes are not valid + */ + decompressPoint(bytes) { + const yTag = bytes[0]; + const expLength = this.CURVE.r.bitLength() / 8 + 1; // bitLength rounds up + if (yTag != 2 && yTag != 3) { + throw new Error('[privacy-pass]: compressed point is invalid, bytes[0] = ' + yTag); + } else if (bytes.length !== expLength) { + throw new Error( + `[privacy-pass]: compressed point is too long, actual = ${bytes.length}, expected = ${expLength}`, + ); + } + const xBytes = bytes.slice(1); + const x = this.CURVE.field.fromBits(sjcl.codec.bytes.toBits(xBytes)).normalize(); + const sign = yTag & 1; + + // y^2 = x^3 - 3x + b (mod p) + let rh = x.power(3); + const threeTimesX = x.mul(this.CURVE.a); + rh = rh.add(threeTimesX).add(this.CURVE.b).mod(this.CURVE.field.modulus); // mod() normalizes + + // modsqrt(z) for p = 3 mod 4 is z^(p+1/4) + const sqrt = this.CURVE.field.modulus.add(1).normalize().halveM().halveM(); + let y = new this.CURVE.field(rh.powermod(sqrt, this.CURVE.field.modulus)); + + const parity = y.limbs[0] & 1; + if (parity != sign) { + y = this.CURVE.field.modulus.sub(y).normalize(); + } + + const point = new sjcl.ecc.point(this.CURVE, x, y); + if (!point.isValid()) { + // we return null here rather than an error as we iterate over this + // method during hash-and-inc + return null; + } + return point; + } + + /** + * Parse a PEM-encoded public key. + * @param {string} pemPublicKey - A public key in PEM format. + * @return {sjcl.ecc.ecdsa.publicKey} a public key for sjcl library. + */ + parsePublicKeyfromPEM(pemPublicKey) { + try { + let bytes = PEM.parseBlock(pemPublicKey); + let json = ASN1.parse(bytes.der); + let xy = json.children[1].value; + const point = this.sec1DecodeFromBytes(xy); + return new sjcl.ecc.ecdsa.publicKey(this.CURVE, point); + } catch (e) { + throw new Error('[privacy-pass]: Failed on parsing public key. ' + e.message); + } + } + + /** + * Verify the signature of the retrieved configuration portion. + * @param {Number} cfgId - ID of configuration being used. + * @param {json} config - commitments to verify + * @return {boolean} True, if the commitment has valid signature and is not + * expired; otherwise, throws an exception. + */ + verifyConfiguration(publicKey, config, signature) { + const sig = parseSignaturefromPEM(signature); + const msg = JSON.stringify(config); + const pk = this.parsePublicKeyfromPEM(publicKey); + const hmsg = sjcl.hash.sha256.hash(msg); + try { + return pk.verify(hmsg, sig); + } catch (error) { + throw new Error('[privacy-pass]: Invalid configuration verification.'); + } + } + + /** + * DLEQ proof verification logic + */ + + /** + * Verify the DLEQ proof object using the information provided + * @param {string} proofObj base64-encoded batched DLEQ proof object + * @param {Object} tokens array of token objects containing blinded curve points + * @param {Array} signatures an array of signed points + * @param {Object} commitments JSON object containing encoded curve points + * @param {string} prngName name of the PRNG used for verifying proof + * @return {boolean} + */ + verifyProof(proofObj, tokens, signatures, commitments, prngName) { + const bp = getMarshaledBatchProof(proofObj); + const dleq = retrieveProof(bp); + if (!dleq) { + // Error has probably occurred + return false; + } + if (tokens.length !== signatures.points.length) { + return false; + } + const pointG = this.sec1DecodeFromBase64(commitments.G); + const pointH = this.sec1DecodeFromBase64(commitments.H); + + // Recompute A and B for proof verification + const cH = _scalarMult(dleq.C, pointH); + const rG = _scalarMult(dleq.R, pointG); + const A = cH.toJac().add(rG).toAffine(); + + const composites = this.recomputeComposites(tokens, signatures, pointG, pointH, prngName); + const cZ = _scalarMult(dleq.C, composites.Z); + const rM = _scalarMult(dleq.R, composites.M); + const B = cZ.toJac().add(rM).toAffine(); + + // Recalculate C' and check if C =?= C' + const h = new this.CURVE_H2C_HASH(); // use the h2c hash for convenience + h.update(sec1EncodeToBits(pointG, signatures.compressed)); + h.update(sec1EncodeToBits(pointH, signatures.compressed)); + h.update(sec1EncodeToBits(composites.M, signatures.compressed)); + h.update(sec1EncodeToBits(composites.Z, signatures.compressed)); + h.update(sec1EncodeToBits(A, signatures.compressed)); + h.update(sec1EncodeToBits(B, signatures.compressed)); + const digestBits = h.finalize(); + const receivedDigestBits = dleq.C.toBits(); + if (!sjcl.bitArray.equal(digestBits, receivedDigestBits)) { + console.error(DIGEST_INEQUALITY_ERR); + console.error('Computed digest: ' + digestBits.toString()); + console.error('Received digest: ' + receivedDigestBits.toString()); + return false; + } + return true; + } + + /** + * Recompute the composite M and Z values for verifying DLEQ + * @param {Array} tokens array of token objects containing blinded curve points + * @param {Object} signatures contains array of signed curve points and compression flag + * @param {sjcl.ecc.point} pointG curve point + * @param {sjcl.ecc.point} pointH curve point + * @param {string} prngName name of PRNG used to verify proof + * @return {Object} Object containing composite points M and Z + */ + recomputeComposites(tokens, signatures, pointG, pointH, prngName) { + const seed = this.computeSeed(tokens, signatures, pointG, pointH); + let cM = new sjcl.ecc.pointJac(this.CURVE); // can only add points in jacobian representation + let cZ = new sjcl.ecc.pointJac(this.CURVE); + const prng = { name: prngName }; + switch (prng.name) { + case 'shake': + prng.func = createShake256(); + prng.func.update(seed, 'hex'); + break; + case 'hkdf': + prng.func = evaluateHkdf; + break; + default: + throw new Error(`Server specified PRNG is not compatible: ${prng.name}`); + } + let iter = -1; + for (let i = 0; i < tokens.length; i++) { + iter++; + const ci = this.computePRNGScalar(prng, seed, new sjcl.bn(iter).toBits()); + // Moved this check out of computePRNGScalar to here + if (ci.greaterEquals(this.CURVE.r)) { + i--; + continue; + } + const cMi = _scalarMult(ci, tokens[i].point); + const cZi = _scalarMult(ci, signatures.points[i]); + cM = cM.add(cMi); + cZ = cZ.add(cZi); + } + return { M: cM.toAffine(), Z: cZ.toAffine() }; + } + + /** + * Computes an output of a PRNG (using the seed if it is HKDF) as a sjcl bn + * object + * @param {Object} prng PRNG object for generating output + * @param {string} seed hex-encoded seed + * @param {sjcl.bitArray} salt optional salt for each PRNG eval + * @return {sjcl.bn} PRNG output as scalar value + */ + computePRNGScalar(prng, seed, salt) { + const bitLen = this.CURVE.r.bitLength(); + const mask = MASK[bitLen % 8]; + let out; + switch (prng.name) { + case 'shake': + out = prng.func.squeeze(32, 'hex'); + break; + case 'hkdf': + out = sjcl.codec.hex.fromBits( + prng.func( + sjcl.codec.hex.toBits(seed), + bitLen / 8, + sjcl.codec.utf8String.toBits('DLEQ_PROOF'), + salt, + this.CURVE_H2C_HASH, + ), + ); + break; + default: + throw new Error(`Server specified PRNG is not compatible: ${prng.name}`); + } + // Masking is not strictly necessary for p256 but better to be completely + // compatible in case that the curve changes + const h = parseInt(out.substr(0, 2), 16); + const mh = sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits([h & mask])); + out = mh + out.substr(2); + const nOut = getBigNumFromHex(out); + return nOut; + } + + /** + * Computes a seed for the PRNG for verifying batch DLEQ proofs + * @param {Object} chkM array of token objects containing blinded curve points + * @param {sjcl.ecc.point[]} chkZ array of signed curve points + * @param {sjcl.ecc.point} pointG curve point + * @param {sjcl.ecc.point} pointH curve point + * @return {string} hex-encoded PRNG seed + */ + computeSeed(chkM, chkZ, pointG, pointH) { + const compressed = chkZ.compressed; + const h = new this.CURVE_H2C_HASH(); // we use the h2c hash for convenience + h.update(sec1EncodeToBits(pointG, compressed)); + h.update(sec1EncodeToBits(pointH, compressed)); + for (let i = 0; i < chkM.length; i++) { + h.update(sec1EncodeToBits(chkM[i].point, compressed)); + h.update(sec1EncodeToBits(chkZ.points[i], compressed)); + } + return sjcl.codec.hex.fromBits(h.finalize()); + } + + /** + * Derives the shared key used for redemption MACs + * @param {sjcl.ecc.point} N Signed curve point associated with token + * @param {Object} token client-generated token data + * @return {sjcl.codec.bytes} bytes of derived key + */ + deriveKey(N, token) { + // the exact bits of the string "hash_derive_key" + const tagBits = sjcl.codec.hex.toBits('686173685f6465726976655f6b6579'); + const hash = this.getActiveECSettings().hash; + const h = new sjcl.misc.hmac(tagBits, hash); + + // Always compute derived key using uncompressed point bytes + const encodedPoint = sec1Encode(N, false); + const tokenBits = sjcl.codec.bytes.toBits(token); + const pointBits = sjcl.codec.bytes.toBits(encodedPoint); + + h.update(tokenBits); + h.update(pointBits); + + const keyBytes = sjcl.codec.bytes.fromBits(h.digest()); + return keyBytes; + } + + createRequestBinding(key, data) { + // the exact bits of the string "hash_request_binding" + const tagBits = sjcl.codec.utf8String.toBits('hash_request_binding'); + const keyBits = sjcl.codec.bytes.toBits(key); + const hash = this.getActiveECSettings().hash; + + const h = new sjcl.misc.hmac(keyBits, hash); + h.update(tagBits); + + let dataBits = null; + for (let i = 0; i < data.length; i++) { + dataBits = sjcl.codec.bytes.toBits(data[i]); + h.update(dataBits); + } + + return sjcl.codec.base64.fromBits(h.digest()); + } + + /** + * Decodes the received curve points + * @param {Array} signatures An array of base64-encoded signed points + * @return {Object} object containing array of curve points and compression flag + */ + getCurvePoints(signatures) { + const self = this; + const compression = { on: false, set: false }; + const sigBytes = []; + signatures.forEach(function (signature) { + const buf = sjcl.codec.bytes.fromBits(sjcl.codec.base64.toBits(signature)); + let setting = false; + switch (buf[0]) { + case 2: + case 3: + setting = true; + break; + case 4: + // do nothing + break; + default: + throw new Error(`[privacy-pass]: point, ${buf}, is not encoded correctly`); + } + if (!validResponseCompression(compression, setting)) { + throw new Error('[privacy-pass]: inconsistent point compression in server response'); + } + sigBytes.push(buf); + }); + + const usablePoints = []; + sigBytes.forEach(function (buf) { + const usablePoint = self.sec1DecodeFromBytes(buf); + if (usablePoint == null) { + throw new Error('[privacy-pass]: unable to decode point: ' + buf); + } + usablePoints.push(usablePoint); + }); + return { points: usablePoints, compressed: compression.on }; + } + + /** + * DEPRECATED: Method for hashing to curve based on the principal of attempting + * to hash the bytes multiple times and recover a curve point. Has non-negligble + * probailistic failure conditions. + * @param {sjcl.bitArray} seed + * @param {sjcl.hash} hash hash function for hashing bytes to base field + * @param {sjcl.bitArray} label + * @return {sjcl.ecc.point} returns a curve point on the active curve + */ + hashAndInc(seed, hash, label) { + const h = new hash(); + + // Need to match the Go curve hash, so we decode the exact bytes of the + // string "1.2.840.100045.3.1.7 point generation seed" instead of relying + // on the utf8 codec that didn't match. + const separator = label; + + h.update(separator); + + let i = 0; + // Increased increments to decrease chance of failure + for (i = 0; i < 20; i++) { + // little endian uint32 + const ctr = new Uint8Array(4); + // typecast hack: number -> Uint32, bitwise Uint8 + ctr[0] = (i >>> 0) & 0xff; + const ctrBits = sjcl.codec.bytes.toBits(ctr); + + // H(s||ctr) + h.update(seed); + h.update(ctrBits); + + const digestBits = h.finalize(); + const bytes = sjcl.codec.bytes.fromBits(digestBits); + + // attempt to decompress a point with a valid tag (don't need to try + // 0x03 because this is just the negative version) + // curve choice is implicit based on active curve parameters + const point = this.sec1DecodeFromBytes([2].concat(bytes)); + if (point !== null) { + return point; + } + + seed = digestBits; + h.reset(); + } + + throw new Error('Unable to construct point using hash and increment'); + } + + /** + * hashes bits to the chosen elliptic curve + * @param {sjcl.bitArray} alpha bits to be encoded onto curve + * @param {Object} ecSettings the curve settings being used by the extension + * @return {sjcl.ecc.point} point on curve + */ + h2Curve(alpha, ecSettings) { + let point; + switch (ecSettings.method) { + case 'swu': + point = simplifiedSWU(alpha, ecSettings.curve, ecSettings.hash, ecSettings.label); + break; + case 'increment': + point = this.hashAndInc(alpha, ecSettings.hash, ecSettings.label); + break; + default: + throw new Error( + '[privacy-pass]: Incompatible curve chosen for hashing, SJCL chosen curve: ' + + sjcl.ecc.curveName(ecSettings.curve), + ); + } + return point; + } +} + +// ----------------------------------------------------------------------------- +/* summary of exports: + +module.exports = { + // data values: + defaultECSettings, + + // functions: + sec1Encode, + sec1EncodeToBase64, + getBigNumFromBytes, + newBigNum, + getBytesFromString, + getBase64FromBytes, + getBase64FromString, + + // classes: + VOPRF, +} + +*/ +// ----------------------------------------------------------------------------- diff --git a/src/background/crypto/voprf.test.ts b/src/background/crypto/voprf.test.ts new file mode 100644 index 00000000..d7692aec --- /dev/null +++ b/src/background/crypto/voprf.test.ts @@ -0,0 +1,8 @@ +import { VOPRF, defaultECSettings } from './voprf'; + +test('randomPoint', () => { + const voprf = new VOPRF(defaultECSettings); + const P = voprf.newRandomPoint(); + + expect(P).toBeDefined(); +}); diff --git a/src/background/index.ts b/src/background/index.ts index 5f8dfbe0..8e5c0630 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -21,12 +21,6 @@ import { handleReceivedMessage, } from './listeners/messageListener'; -import * as voprf from './voprf'; - -/* Initialize shared modules */ - -voprf.initECSettings(voprf.defaultECSettings); - /* Local state */ declare global { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 0866ae10..77f8eb4d 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -1,4 +1,4 @@ -import * as voprf from '../voprf'; +import * as voprf from '../crypto/voprf'; import { Provider, EarnedTokenCookie, Callbacks } from './provider'; import { Storage } from '../storage'; @@ -38,6 +38,7 @@ export class CloudflareProvider extends Provider { name: 'cf_clearance' }; + private VOPRF: voprf.VOPRF; private callbacks: Callbacks; private storage: Storage; private redeemInfo: RedeemInfo | null; @@ -45,6 +46,7 @@ export class CloudflareProvider extends Provider { constructor(storage: Storage, callbacks: Callbacks) { super(storage, callbacks); + this.VOPRF = new voprf.VOPRF(voprf.defaultECSettings); this.callbacks = callbacks; this.storage = storage; this.redeemInfo = null; @@ -92,7 +94,7 @@ export class CloudflareProvider extends Provider { } // This will throw an error on a bad signature. - voprf.verifyConfiguration( + this.VOPRF.verifyConfiguration( VERIFICATION_KEY, { H: commitment.H, @@ -103,7 +105,7 @@ export class CloudflareProvider extends Provider { // Cache. const item = { - G: voprf.sec1EncodeToBase64(voprf.getActiveECSettings().curve.G, false), + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), H: commitment.H, }; this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); @@ -152,11 +154,11 @@ export class CloudflareProvider extends Provider { } const data: SignaturesParam = JSON.parse(atob(signatures)); - const returned = voprf.getCurvePoints(data.sigs); + const returned = this.VOPRF.getCurvePoints(data.sigs); const commitment = await this.getCommitment(data.version); - const result = voprf.verifyProof( + const result = this.VOPRF.verifyProof( data.proof, tokens.map((token) => token.toLegacy()), returned, @@ -200,7 +202,7 @@ export class CloudflareProvider extends Provider { this.redeemInfo = null; const key = token.getMacKey(); - const binding = voprf.createRequestBinding(key, [ + const binding = this.VOPRF.createRequestBinding(key, [ voprf.getBytesFromString(url.hostname), voprf.getBytesFromString(details.method + ' ' + url.pathname), ]); diff --git a/src/background/token.test.ts b/src/background/token.test.ts index 7cd804c8..ccddf509 100644 --- a/src/background/token.test.ts +++ b/src/background/token.test.ts @@ -1,14 +1,4 @@ import Token from './token'; -import { initECSettings } from './voprf'; - -beforeAll(() => { - // TODO This shouldn't be needed after refactoring the voprf module. - initECSettings({ - curve: 'p256', - hash: 'sha256', - method: 'increment', - }); -}); test('Construct a token', () => { new Token(); diff --git a/src/background/token.ts b/src/background/token.ts index ca4c4e38..9cbffba9 100644 --- a/src/background/token.ts +++ b/src/background/token.ts @@ -1,4 +1,4 @@ -import * as voprf from './voprf'; +import * as voprf from './crypto/voprf'; interface SignedComponent { blindedPoint: voprf.Point; @@ -6,6 +6,8 @@ interface SignedComponent { } export default class Token { + private VOPRF: voprf.VOPRF; + private input: voprf.Bytes; private factor: voprf.BigNum; @@ -14,9 +16,15 @@ export default class Token { private signed: SignedComponent | null; - constructor() { - const { data: input, point: unblindedPoint } = voprf.newRandomPoint(); - const { blind: factor, point: blindedPoint } = voprf.blindPoint(unblindedPoint); + constructor(VOPRF: voprf.VOPRF | void) { + if (VOPRF === undefined) { + VOPRF = new voprf.VOPRF(voprf.defaultECSettings); + } + + this.VOPRF = VOPRF; + + const { data: input, point: unblindedPoint } = this.VOPRF.newRandomPoint(); + const { blind: factor, point: blindedPoint } = this.VOPRF.blindPoint(unblindedPoint); this.input = input; this.factor = factor; @@ -27,22 +35,27 @@ export default class Token { this.signed = null; } - static fromString(str: string): Token { + static fromString(str: string, VOPRF: voprf.VOPRF | void): Token { + if (VOPRF === undefined) { + VOPRF = new voprf.VOPRF(voprf.defaultECSettings); + } + const json = JSON.parse(str); const token: Token = Object.create(Token.prototype); - token.input = json.input; + token.VOPRF = VOPRF; + token.input = json.input; token.factor = voprf.newBigNum(json.factor); - token.blindedPoint = voprf.sec1DecodeFromBase64(json.blindedPoint); - token.unblindedPoint = voprf.sec1DecodeFromBase64(json.unblindedPoint); + token.blindedPoint = VOPRF.sec1DecodeFromBase64(json.blindedPoint); + token.unblindedPoint = VOPRF.sec1DecodeFromBase64(json.unblindedPoint); token.signed = json.signed !== null ? { - blindedPoint: voprf.sec1DecodeFromBase64(json.signed.blindedPoint), - unblindedPoint: voprf.sec1DecodeFromBase64(json.signed.unblindedPoint), + blindedPoint: VOPRF.sec1DecodeFromBase64(json.signed.blindedPoint), + unblindedPoint: VOPRF.sec1DecodeFromBase64(json.signed.unblindedPoint), } : null; @@ -50,8 +63,8 @@ export default class Token { } setSignedPoint(point: voprf.Point): void { - const blindedPoint = point; - const unblindedPoint = voprf.unblindPoint(this.factor, point); + const blindedPoint = point; + const unblindedPoint = this.VOPRF.unblindPoint(this.factor, point); this.signed = { blindedPoint, @@ -76,7 +89,7 @@ export default class Token { if (this.signed === null) { throw new Error('Unsigned token is used to derive a MAC key'); } - return voprf.deriveKey(this.signed.unblindedPoint, this.input); + return this.VOPRF.deriveKey(this.signed.unblindedPoint, this.input); } getInput(): voprf.Bytes { diff --git a/src/background/voprf.d.ts b/src/background/voprf.d.ts deleted file mode 100644 index 9c4730c4..00000000 --- a/src/background/voprf.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -export type Point = unknown; -export type Bytes = unknown; -export type BigNum = { toString(): string }; - -export type Curve = 'p256'; -export type Hash = 'sha256'; -export type HashMethod = 'increment' | 'swu'; - -export interface ECSettings { - curve: any; - hash: Hash; - method: HashMethod; -} - -export const defaultECSettings: ECSettings; - -// TODO This should be implemented in a new Point class. -export function blindPoint(point: Point): { blind: BigNum; point: Point }; -// TODO This should be implemented in a new Point class. -export function newRandomPoint(): { data: Bytes; point: Point }; - -export function getActiveECSettings(): ECSettings; - -export function initECSettings(params: { curve: Curve; hash: Hash; method: HashMethod }): void; - -// TODO This should be implemented in a new Point class. -export function getCurvePoints(signatures: string[]): { - points: Point[]; - compressed: boolean; -}; - -// TODO This should be implemented in a new Point class. -export function sec1EncodeToBase64(point: Point, compressed: boolean): string; -// TODO This should be implemented in a new Point class. -export function sec1DecodeFromBase64(encoded: string): Point; - -export function verifyConfiguration(publicKey: string, config: any, signature: string): boolean; -// TODO Proof verification should be inside Token class. -export function verifyProof( - proof: string, - tokens: unknown[], - signatures: { points: Point[]; compressed: boolean }, - commitments: any, - prngName: any, -): boolean; - -export function unblindPoint(factor: BigNum, blindedPoint: Point): Point; - -export function newBigNum(encoded: string): BigNum; - -export function deriveKey(N: Point, token: any): any; -export function createRequestBinding(key: any, data: any): any; -export function getBytesFromString(str: any): any; -export function getBase64FromString(str: any): any; -export function getBase64FromBytes(bytes: any): any; diff --git a/src/background/voprf.js b/src/background/voprf.js deleted file mode 100644 index 38b19169..00000000 --- a/src/background/voprf.js +++ /dev/null @@ -1,931 +0,0 @@ -/** - * This implements a 2HashDH-based token scheme using the SJCL ecc package. - * - * @author: George Tankersley - * @author: Alex Davidson - */ - -import 'asn1-parser'; - -import sjcl from 'sjcl'; - -let PEM; -let ASN1; -if (typeof window !== 'undefined') { - PEM = window.PEM; - ASN1 = window.ASN1; -} - -export let shake256 = () => { - return createShake256(); -}; - -const BATCH_PROOF_PREFIX = 'batch-proof='; -const MASK = ['0xff', '0x1', '0x3', '0x7', '0xf', '0x1f', '0x3f', '0x7f']; - -const DIGEST_INEQUALITY_ERR = '[privacy-pass]: Recomputed digest does not equal received digest'; -const PARSE_ERR = '[privacy-pass]: Error parsing proof'; - -// Globals for keeping track of EC curve settings -let CURVE; -let CURVE_H2C_HASH; -let CURVE_H2C_METHOD; -let CURVE_H2C_LABEL; - -// 1.2.840.10045.3.1.7 point generation seed -const INC_H2C_LABEL = sjcl.codec.hex.toBits( - '312e322e3834302e31303034352e332e312e3720706f696e742067656e65726174696f6e2073656564', -); -const SSWU_H2C_LABEL = 'H2C-P256-SHA256-SSWU-'; - -export const defaultECSettings = { - curve: 'p256', - hash: 'sha256', - method: 'increment', -}; - -/** - * Sets the curve parameters for the current session based on the contents of - * activeConfig.h2c-params - * @param h2cParams - */ -export function initECSettings(h2cParams) { - const curveStr = h2cParams.curve; - const hashStr = h2cParams.hash; - const methodStr = h2cParams.method; - switch (curveStr) { - case 'p256': - if (methodStr != 'swu' && methodStr != 'increment') { - throw new Error( - "[privacy-pass]: Incompatible h2c method: '" + - methodStr + - "', for curve " + - curveStr, - ); - } else if (hashStr != 'sha256') { - throw new Error( - "[privacy-pass]: Incompatible h2c hash: '" + - hashStr + - "', for curve " + - curveStr, - ); - } - CURVE = sjcl.ecc.curves.c256; - CURVE_H2C_HASH = sjcl.hash.sha256; - CURVE_H2C_METHOD = methodStr; - CURVE_H2C_LABEL = methodStr === 'increment' ? INC_H2C_LABEL : SSWU_H2C_LABEL; - break; - default: - throw new Error('[privacy-pass]: Incompatible curve chosen: ' + curveStr); - } -} - -/** - * Returns the active configuration for the elliptic curve setting - * @return {Object} Object containing the active curve and h2c configuration - */ -export function getActiveECSettings() { - return { curve: CURVE, hash: CURVE_H2C_HASH, method: CURVE_H2C_METHOD, label: CURVE_H2C_LABEL }; -} - -/** - * Multiplies the point P with the scalar k and outputs kP - * @param {sjcl.bn} k scalar - * @param {sjcl.ecc.point} P curve point - * @return {sjcl.ecc.point} - */ -function _scalarMult(k, P) { - const Q = P.mult(k); - return Q; -} - -/** - * Samples a random scalar and uses it to blind the point P - * @param {sjcl.ecc.point} P curve point - * @return {sjcl.ecc.point} - */ -export function blindPoint(P) { - const bF = sjcl.bn.random(CURVE.r, 10); - const bP = _scalarMult(bF, P); - return { point: bP, blind: bF }; -} - -/** - * unblindPoint takes an assumed-to-be blinded point Q and an accompanying - * blinding scalar b, then returns the point (1/b)*Q. - * @param {sjcl.bn} b scalar blinding factor - * @param {sjcl.ecc.point} Q curve point - * @return {sjcl.ecc.point} - */ -export function unblindPoint(b, Q) { - const binv = b.inverseMod(CURVE.r); - return _scalarMult(binv, Q); -} - -/** - * Creates a new random point on the curve by sampling random bytes and then - * hashing to the chosen curve. - * @return {sjcl.ecc.point} - */ -export function newRandomPoint() { - const random = crypto.getRandomValues(new Int32Array(8)); - - // Choose hash-to-curve method - const point = h2Curve(random, getActiveECSettings()); - - let t; - if (point) { - t = { data: sjcl.codec.bytes.fromBits(random), point: point }; - } - return t; -} - -/** - * Encodes a curve point as bytes in SEC1 uncompressed format - * @param {sjcl.ecc.point} P - * @param {bool} compressed - * @return {sjcl.codec.bytes} - */ -export function sec1Encode(P, compressed) { - let out = []; - if (!compressed) { - const xyBytes = sjcl.codec.bytes.fromBits(P.toBits()); - out = [0x04].concat(xyBytes); - } else { - const xBytes = sjcl.codec.bytes.fromBits(P.x.toBits()); - const y = P.y.normalize(); - const sign = y.limbs[0] & 1 ? 0x03 : 0x02; - out = [sign].concat(xBytes); - } - return out; -} - -/** - * Encodes a curve point into bits for using as input to hash functions etc - * @param {sjcl.ecc.point} point curve point - * @param {bool} compressed flag indicating whether points have been compressed - * @return {sjcl.bitArray} - */ -function sec1EncodeToBits(point, compressed) { - return sjcl.codec.bytes.toBits(sec1Encode(point, compressed)); -} - -/** - * Encodes a point into a base 64 string - * @param {sjcl.ecc.point} point - * @param {bool} compressed - * @return {string} - */ -export function sec1EncodeToBase64(point, compressed) { - return sjcl.codec.base64.fromBits(sec1EncodeToBits(point, compressed)); -} - -/** - * Decodes a base64-encoded string into a curve point - * @param {string} p a base64-encoded, uncompressed curve point - * @return {sjcl.ecc.point} - */ -export function sec1DecodeFromBase64(p) { - const sec1Bits = sjcl.codec.base64.toBits(p); - const sec1Bytes = sjcl.codec.bytes.fromBits(sec1Bits); - return sec1DecodeFromBytes(sec1Bytes); -} - -/** - * Decodes (SEC1) curve point bytes into a valid curve point - * @param {sjcl.codec.bytes} sec1Bytes bytes of an uncompressed curve point - * @return {sjcl.ecc.point} - */ -export function sec1DecodeFromBytes(sec1Bytes) { - let P; - switch (sec1Bytes[0]) { - case 0x02: - case 0x03: - P = decompressPoint(sec1Bytes); - break; - case 0x04: - P = CURVE.fromBits(sjcl.codec.bytes.toBits(sec1Bytes.slice(1))); - break; - default: - throw new Error( - '[privacy-pass]: attempted sec1 point decoding with incorrect tag: ' + sec1Bytes[0], - ); - } - return P; -} - -/** - * Attempts to decompress a curve point in SEC1 encoded format. Returns null if - * the point is invalid - * @param {sjcl.codec.bytes} bytes bytes of a compressed curve point (SEC1) - * @return {sjcl.ecc.point} may be null if compressed bytes are not valid - */ -function decompressPoint(bytes) { - const yTag = bytes[0]; - const expLength = CURVE.r.bitLength() / 8 + 1; // bitLength rounds up - if (yTag != 2 && yTag != 3) { - throw new Error('[privacy-pass]: compressed point is invalid, bytes[0] = ' + yTag); - } else if (bytes.length !== expLength) { - throw new Error( - `[privacy-pass]: compressed point is too long, actual = ${bytes.length}, expected = ${expLength}`, - ); - } - const xBytes = bytes.slice(1); - const x = CURVE.field.fromBits(sjcl.codec.bytes.toBits(xBytes)).normalize(); - const sign = yTag & 1; - - // y^2 = x^3 - 3x + b (mod p) - let rh = x.power(3); - const threeTimesX = x.mul(CURVE.a); - rh = rh.add(threeTimesX).add(CURVE.b).mod(CURVE.field.modulus); // mod() normalizes - - // modsqrt(z) for p = 3 mod 4 is z^(p+1/4) - const sqrt = CURVE.field.modulus.add(1).normalize().halveM().halveM(); - let y = new CURVE.field(rh.powermod(sqrt, CURVE.field.modulus)); - - const parity = y.limbs[0] & 1; - if (parity != sign) { - y = CURVE.field.modulus.sub(y).normalize(); - } - - const point = new sjcl.ecc.point(CURVE, x, y); - if (!point.isValid()) { - // we return null here rather than an error as we iterate over this - // method during hash-and-inc - return null; - } - return point; -} - -/** - * Decodes the received curve points - * @param {Array} signatures An array of base64-encoded signed points - * @return {Object} object containing array of curve points and compression flag - */ -export function getCurvePoints(signatures) { - const compression = { on: false, set: false }; - const sigBytes = []; - signatures.forEach(function (signature) { - const buf = sjcl.codec.bytes.fromBits(sjcl.codec.base64.toBits(signature)); - let setting = false; - switch (buf[0]) { - case 2: - case 3: - setting = true; - break; - case 4: - // do nothing - break; - default: - throw new Error(`[privacy-pass]: point, ${buf}, is not encoded correctly`); - } - if (!validResponseCompression(compression, setting)) { - throw new Error('[privacy-pass]: inconsistent point compression in server response'); - } - sigBytes.push(buf); - }); - - const usablePoints = []; - sigBytes.forEach(function (buf) { - const usablePoint = sec1DecodeFromBytes(buf); - if (usablePoint == null) { - throw new Error('[privacy-pass]: unable to decode point: ' + buf); - } - usablePoints.push(usablePoint); - }); - return { points: usablePoints, compressed: compression.on }; -} - -/** - * Checks that the signed points from the IssueResponse have consistent - * compression - * @param {Object} compression compression object to be checked for consistency - * @param {bool} setting new setting based on point data - * @return {bool} - */ -function validResponseCompression(compression, setting) { - if (!compression.set) { - compression.on = setting; - compression.set = true; - } else if (compression.on !== setting) { - return false; - } - return true; -} - -// Commitments verification - -/** - * Parse a PEM-encoded signature. - * @param {string} pemSignature - A signature in PEM format. - * @return {sjcl.bitArray} a signature object for sjcl library. - */ -function parseSignaturefromPEM(pemSignature) { - try { - const bytes = PEM.parseBlock(pemSignature); - const json = ASN1.parse(bytes.der); - const r = sjcl.codec.bytes.toBits(json.children[0].value); - const s = sjcl.codec.bytes.toBits(json.children[1].value); - return sjcl.bitArray.concat(r, s); - } catch (e) { - throw new Error('[privacy-pass]: Failed on parsing commitment signature. ' + e.message); - } -} - -/** - * Parse a PEM-encoded public key. - * @param {string} pemPublicKey - A public key in PEM format. - * @return {sjcl.ecc.ecdsa.publicKey} a public key for sjcl library. - */ -function parsePublicKeyfromPEM(pemPublicKey) { - try { - let bytes = PEM.parseBlock(pemPublicKey); - let json = ASN1.parse(bytes.der); - let xy = json.children[1].value; - const point = sec1DecodeFromBytes(xy); - return new sjcl.ecc.ecdsa.publicKey(CURVE, point); - } catch (e) { - throw new Error('[privacy-pass]: Failed on parsing public key. ' + e.message); - } -} - -/** - * Verify the signature of the retrieved configuration portion. - * @param {Number} cfgId - ID of configuration being used. - * @param {json} config - commitments to verify - * @return {boolean} True, if the commitment has valid signature and is not - * expired; otherwise, throws an exception. - */ -export function verifyConfiguration(publicKey, config, signature) { - const sig = parseSignaturefromPEM(signature); - const msg = JSON.stringify(config); - const pk = parsePublicKeyfromPEM(publicKey); - const hmsg = sjcl.hash.sha256.hash(msg); - try { - return pk.verify(hmsg, sig); - } catch (error) { - throw new Error('[privacy-pass]: Invalid configuration verification.'); - } -} - -/** - * DLEQ proof verification logic - */ - -/** - * Verify the DLEQ proof object using the information provided - * @param {string} proofObj base64-encoded batched DLEQ proof object - * @param {Object} tokens array of token objects containing blinded curve points - * @param {Array} signatures an array of signed points - * @param {Object} commitments JSON object containing encoded curve points - * @param {string} prngName name of the PRNG used for verifying proof - * @return {boolean} - */ -export function verifyProof(proofObj, tokens, signatures, commitments, prngName) { - const bp = getMarshaledBatchProof(proofObj); - const dleq = retrieveProof(bp); - if (!dleq) { - // Error has probably occurred - return false; - } - if (tokens.length !== signatures.points.length) { - return false; - } - const pointG = sec1DecodeFromBase64(commitments.G); - const pointH = sec1DecodeFromBase64(commitments.H); - - // Recompute A and B for proof verification - const cH = _scalarMult(dleq.C, pointH); - const rG = _scalarMult(dleq.R, pointG); - const A = cH.toJac().add(rG).toAffine(); - - const composites = recomputeComposites(tokens, signatures, pointG, pointH, prngName); - const cZ = _scalarMult(dleq.C, composites.Z); - const rM = _scalarMult(dleq.R, composites.M); - const B = cZ.toJac().add(rM).toAffine(); - - // Recalculate C' and check if C =?= C' - const h = new CURVE_H2C_HASH(); // use the h2c hash for convenience - h.update(sec1EncodeToBits(pointG, signatures.compressed)); - h.update(sec1EncodeToBits(pointH, signatures.compressed)); - h.update(sec1EncodeToBits(composites.M, signatures.compressed)); - h.update(sec1EncodeToBits(composites.Z, signatures.compressed)); - h.update(sec1EncodeToBits(A, signatures.compressed)); - h.update(sec1EncodeToBits(B, signatures.compressed)); - const digestBits = h.finalize(); - const receivedDigestBits = dleq.C.toBits(); - if (!sjcl.bitArray.equal(digestBits, receivedDigestBits)) { - console.error(DIGEST_INEQUALITY_ERR); - console.error('Computed digest: ' + digestBits.toString()); - console.error('Received digest: ' + receivedDigestBits.toString()); - return false; - } - return true; -} - -/** - * Recompute the composite M and Z values for verifying DLEQ - * @param {Array} tokens array of token objects containing blinded curve points - * @param {Object} signatures contains array of signed curve points and compression flag - * @param {sjcl.ecc.point} pointG curve point - * @param {sjcl.ecc.point} pointH curve point - * @param {string} prngName name of PRNG used to verify proof - * @return {Object} Object containing composite points M and Z - */ -function recomputeComposites(tokens, signatures, pointG, pointH, prngName) { - const seed = computeSeed(tokens, signatures, pointG, pointH); - let cM = new sjcl.ecc.pointJac(CURVE); // can only add points in jacobian representation - let cZ = new sjcl.ecc.pointJac(CURVE); - const prng = { name: prngName }; - switch (prng.name) { - case 'shake': - prng.func = shake256(); - prng.func.update(seed, 'hex'); - break; - case 'hkdf': - prng.func = evaluateHkdf; - break; - default: - throw new Error(`Server specified PRNG is not compatible: ${prng.name}`); - } - let iter = -1; - for (let i = 0; i < tokens.length; i++) { - iter++; - const ci = computePRNGScalar(prng, seed, new sjcl.bn(iter).toBits()); - // Moved this check out of computePRNGScalar to here - if (ci.greaterEquals(CURVE.r)) { - i--; - continue; - } - const cMi = _scalarMult(ci, tokens[i].point); - const cZi = _scalarMult(ci, signatures.points[i]); - cM = cM.add(cMi); - cZ = cZ.add(cZi); - } - return { M: cM.toAffine(), Z: cZ.toAffine() }; -} - -/** - * Computes an output of a PRNG (using the seed if it is HKDF) as a sjcl bn - * object - * @param {Object} prng PRNG object for generating output - * @param {string} seed hex-encoded seed - * @param {sjcl.bitArray} salt optional salt for each PRNG eval - * @return {sjcl.bn} PRNG output as scalar value - */ -function computePRNGScalar(prng, seed, salt) { - const bitLen = CURVE.r.bitLength(); - const mask = MASK[bitLen % 8]; - let out; - switch (prng.name) { - case 'shake': - out = prng.func.squeeze(32, 'hex'); - break; - case 'hkdf': - out = sjcl.codec.hex.fromBits( - prng.func( - sjcl.codec.hex.toBits(seed), - bitLen / 8, - sjcl.codec.utf8String.toBits('DLEQ_PROOF'), - salt, - CURVE_H2C_HASH, - ), - ); - break; - default: - throw new Error(`Server specified PRNG is not compatible: ${prng.name}`); - } - // Masking is not strictly necessary for p256 but better to be completely - // compatible in case that the curve changes - const h = parseInt(out.substr(0, 2), 16); - const mh = sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits([h & mask])); - out = mh + out.substr(2); - const nOut = getBigNumFromHex(out); - return nOut; -} - -/** - * Computes a seed for the PRNG for verifying batch DLEQ proofs - * @param {Object} chkM array of token objects containing blinded curve points - * @param {sjcl.ecc.point[]} chkZ array of signed curve points - * @param {sjcl.ecc.point} pointG curve point - * @param {sjcl.ecc.point} pointH curve point - * @return {string} hex-encoded PRNG seed - */ -function computeSeed(chkM, chkZ, pointG, pointH) { - const compressed = chkZ.compressed; - const h = new CURVE_H2C_HASH(); // we use the h2c hash for convenience - h.update(sec1EncodeToBits(pointG, compressed)); - h.update(sec1EncodeToBits(pointH, compressed)); - for (let i = 0; i < chkM.length; i++) { - h.update(sec1EncodeToBits(chkM[i].point, compressed)); - h.update(sec1EncodeToBits(chkZ.points[i], compressed)); - } - return sjcl.codec.hex.fromBits(h.finalize()); -} - -/** - * hkdf - The HMAC-based Key Derivation Function - * based on https://github.com/mozilla/node-hkdf - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * @param {bitArray} ikm Initial keying material - * @param {integer} length Length of the derived key in bytes - * @param {bitArray} info Key derivation data - * @param {bitArray} salt Salt - * @param {sjcl.hash} hash hash function - * @return {bitArray} - */ -function evaluateHkdf(ikm, length, info, salt, hash) { - const mac = new sjcl.misc.hmac(salt, hash); - mac.update(ikm); - const prk = mac.digest(); - - const hashLength = Math.ceil(sjcl.bitArray.bitLength(prk) / 8); - const numBlocks = Math.ceil(length / hashLength); - if (numBlocks > 255) { - throw new Error( - `[privacy-pass]: HKDF error, number of proposed iterations too large: ${numBlocks}`, - ); - } - - let prev = sjcl.codec.hex.toBits(''); - let output = ''; - for (let i = 0; i < numBlocks; i++) { - const hmac = new sjcl.misc.hmac(prk, hash); - const input = sjcl.bitArray.concat( - sjcl.bitArray.concat(prev, info), - sjcl.codec.utf8String.toBits(String.fromCharCode(i + 1)), - ); - hmac.update(input); - prev = hmac.digest(); - output += sjcl.codec.hex.fromBits(prev); - } - return sjcl.bitArray.clamp(sjcl.codec.hex.toBits(output), length * 8); -} - -/** - * Returns a decoded DLEQ proof as an object that can be verified - * @param {Object} bp batch proof as encoded JSON - * @return {Object} DLEQ proof object - */ -function retrieveProof(bp) { - let dleqProof; - try { - dleqProof = parseDleqProof(atob(bp.P)); - } catch (e) { - console.error(`${PARSE_ERR}: ${e}`); - return; - } - return dleqProof; -} - -/** - * Decode proof string and remove prefix - * @param {string} proof base64-encoded batched DLEQ proof - * @return {Object} JSON batched DLEQ proof - */ -function getMarshaledBatchProof(proof) { - let proofStr = atob(proof); - if (proofStr.indexOf(BATCH_PROOF_PREFIX) === 0) { - proofStr = proofStr.substring(BATCH_PROOF_PREFIX.length); - } - return JSON.parse(proofStr); -} - -/** - * Decode the proof that is sent into an Object - * @param {string} proofStr proof JSON as string - * @return {Object} - */ -function parseDleqProof(proofStr) { - const dleqProofM = JSON.parse(proofStr); - const dleqProof = {}; - dleqProof.R = getBigNumFromB64(dleqProofM.R); - dleqProof.C = getBigNumFromB64(dleqProofM.C); - return dleqProof; -} - -/** - * Return a bignum from a base64-encoded string - * @param {string} b64Str - * @return {sjcl.bn} - */ -function getBigNumFromB64(b64Str) { - const bits = sjcl.codec.base64.toBits(b64Str); - return sjcl.bn.fromBits(bits); -} - -/** - * Return a big number from an array of bytes - * @param {sjcl.codec.bytes} bytes - * @return {sjcl.bn} - */ -export function getBigNumFromBytes(bytes) { - const bits = sjcl.codec.bytes.toBits(bytes); - return sjcl.bn.fromBits(bits); -} - -/** - * Return a big number from hex-encoded string - * @param {string} hex hex-encoded string - * @return {sjcl.bn} - */ -function getBigNumFromHex(hex) { - return sjcl.bn.fromBits(sjcl.codec.hex.toBits(hex)); -} - -const p256Curve = sjcl.ecc.curves.c256; -const precomputedP256 = { - // a=-3, but must be reduced mod p for P256; otherwise, - // inverseMod function loops forever. - A: p256Curve.a.fullReduce(), - B: p256Curve.b, - baseField: p256Curve.field, - c1: p256Curve.b.mul(-1).mul(p256Curve.a.inverseMod(p256Curve.field.modulus)), - c2: p256Curve.field.modulus.sub(1).cnormalize().halveM(), - sqrt: p256Curve.field.modulus.add(1).cnormalize().halveM().halveM(), -}; - -/** - * Converts the number x into a byte array of length n - * @param {Number} x - * @param {Number} n - * @return {sjcl.codec.bytes} - */ -function i2osp(x, n) { - const bytes = []; - for (let i = n - 1; i > -1; i--) { - bytes[i] = x & 0xff; - x = x >> 8; - } - - if (x > 0) { - throw new Error(`[privacy-pass]: number to convert (${x}) is too long for ${n} bytes.`); - } - return bytes; -} - -/** - * hashes bits to the base field (as described in - * draft-irtf-cfrg-hash-to-curve) - * @param {sjcl.bitArray} x bits of element to be translated - * @param {sjcl.ecc.curve} curve elliptic curve - * @param {sjcl.hash} hash hash function object - * @param {string} label context label for domain separation - * @return {int} integer in the base field of curve - */ -function h2Base(x, curve, hash, label) { - const dataLen = sjcl.codec.bytes.fromBits(x).length; - const h = new hash(); - h.update('h2b'); - h.update(label); - h.update(sjcl.codec.bytes.toBits(i2osp(dataLen, 4))); - h.update(x); - const t = h.finalize(); - const y = curve.field.fromBits(t).cnormalize(); - return y; -} - -/** - * hashes bits to the chosen elliptic curve - * @param {sjcl.bitArray} alpha bits to be encoded onto curve - * @param {Object} ecSettings the curve settings being used by the extension - * @return {sjcl.ecc.point} point on curve - */ -function h2Curve(alpha, ecSettings) { - let point; - switch (ecSettings.method) { - case 'swu': - point = simplifiedSWU(alpha, ecSettings.curve, ecSettings.hash, ecSettings.label); - break; - case 'increment': - point = hashAndInc(alpha, ecSettings.hash, ecSettings.label); - break; - default: - throw new Error( - '[privacy-pass]: Incompatible curve chosen for hashing, SJCL chosen curve: ' + - sjcl.ecc.curveName(ecSettings.curve), - ); - } - return point; -} - -/** - * hashes bits onto affine curve point using simplified SWU encoding algorithm - * Not constant-time due to conditional check - * @param {sjcl.bitArray} alpha bits to be encoded - * @param {sjcl.ecc.curve} activeCurve elliptic curve - * @param {sjcl.hash} hash hash function for hashing bytes to base field - * @param {String} label - * @return {sjcl.ecc.point} curve point - */ -function simplifiedSWU(alpha, activeCurve, hash, label) { - const params = getCurveParams(activeCurve); - const u = h2Base(alpha, activeCurve, hash, label); - const { X, Y } = computeSWUCoordinates(u, params); - const point = new sjcl.ecc.point(activeCurve, X, Y); - if (!point.isValid()) { - throw new Error(`[privacy-pass]: Generated point is not on curve, X: ${X}, Y: ${Y}`); - } - return point; -} - -/** - * Compute (X,Y) coordinates from integer u - * Operations taken from draft-irtf-cfrg-hash-to-curve.txt at commit - * cea8485220812a5d371deda25b5eca96bd7e6c0e - * @param {sjcl.bn} u integer to map - * @param {Object} params curve parameters - * @return {Object} curve coordinates - */ -function computeSWUCoordinates(u, params) { - const { A, B, baseField, c1, c2, sqrt } = params; - const p = baseField.modulus; - const t1 = u.square().mul(-1); // steps 2-3 - const t2 = t1.square(); // step 4 - let x1 = t2.add(t1); // step 5 - x1 = x1.inverse(); // step 6 - x1 = x1.add(1); // step 7 - x1 = x1.mul(c1); // step 8 - - let gx1 = x1.square().mod(p); // steps 9-12 - gx1 = gx1.add(A); - gx1 = gx1.mul(x1); - gx1 = gx1.add(B); - gx1 = gx1.mod(p); - - const x2 = t1.mul(x1); // step 13 - let gx2 = x2.square().mod(p); // step 14-17 - gx2 = gx2.add(A); - gx2 = gx2.mul(x2); - gx2 = gx2.add(B); - gx2 = gx2.mod(p); - - const e = new baseField(gx1.powermod(c2, p)).equals(new sjcl.bn(1)); // step 18 - const X = cmov(x2, x1, e, baseField); // step 19 - const gx = cmov(gx2, gx1, e, baseField); // step 20 - let y1 = gx.powermod(sqrt, p); // step 21 - // choose the positive (the smallest) root - const r = c2.greaterEquals(y1); - let y2 = y1.mul(-1).mod(p); - const Y = cmov(y2, y1, r, baseField); - return { X: X, Y: Y }; -} - -/** - * Return the parameters for the active curve - * @param {sjcl.ecc.curve} curve elliptic curve - * @return {p;A;B} - */ -function getCurveParams(curve) { - let curveParams; - switch (sjcl.ecc.curveName(curve)) { - case 'c256': - curveParams = precomputedP256; - break; - default: - throw new Error( - '[privacy-pass]: Incompatible curve chosen for H2C: ' + sjcl.ecc.curveName(curve), - ); - } - return curveParams; -} - -/** - * DEPRECATED: Method for hashing to curve based on the principal of attempting - * to hash the bytes multiple times and recover a curve point. Has non-negligble - * probailistic failure conditions. - * @param {sjcl.bitArray} seed - * @param {sjcl.hash} hash hash function for hashing bytes to base field - * @param {sjcl.bitArray} label - * @return {sjcl.ecc.point} returns a curve point on the active curve - */ -function hashAndInc(seed, hash, label) { - const h = new hash(); - - // Need to match the Go curve hash, so we decode the exact bytes of the - // string "1.2.840.100045.3.1.7 point generation seed" instead of relying - // on the utf8 codec that didn't match. - const separator = label; - - h.update(separator); - - let i = 0; - // Increased increments to decrease chance of failure - for (i = 0; i < 20; i++) { - // little endian uint32 - const ctr = new Uint8Array(4); - // typecast hack: number -> Uint32, bitwise Uint8 - ctr[0] = (i >>> 0) & 0xff; - const ctrBits = sjcl.codec.bytes.toBits(ctr); - - // H(s||ctr) - h.update(seed); - h.update(ctrBits); - - const digestBits = h.finalize(); - const bytes = sjcl.codec.bytes.fromBits(digestBits); - - // attempt to decompress a point with a valid tag (don't need to try - // 0x03 because this is just the negative version) - // curve choice is implicit based on active curve parameters - const point = sec1DecodeFromBytes([2].concat(bytes)); - if (point !== null) { - return point; - } - - seed = digestBits; - h.reset(); - } - - throw new Error('Unable to construct point using hash and increment'); -} - -/** - * Conditional move selects x or y depending on the bit input. - * @param {sjcl.bn} x is a big number - * @param {sjcl.bn} y is a big number - * @param {boolean} b is a bit - * @param {sjcl.bn} field is the prime field used. - * @return {sjcl.bn} returns x is b=0, otherwise return y. - */ -function cmov(x, y, b, field) { - let z = new field(); - const m = z.radixMask; - const m0 = m & (m + b); - const m1 = m & (m + !b); - x.fullReduce(); - y.fullReduce(); - for (let i = Math.max(x.limbs.length, y.limbs.length) - 1; i >= 0; i--) { - z.limbs.unshift((x.getLimb(i) & m0) ^ (y.getLimb(i) & m1)); - } - return z.mod(field.modulus); -} - -export function newBigNum(s) { - return new sjcl.bn(s); -} - -/** - * Derives the shared key used for redemption MACs - * @param {sjcl.ecc.point} N Signed curve point associated with token - * @param {Object} token client-generated token data - * @return {sjcl.codec.bytes} bytes of derived key - */ -export function deriveKey(N, token) { - // the exact bits of the string "hash_derive_key" - const tagBits = sjcl.codec.hex.toBits('686173685f6465726976655f6b6579'); - const hash = getActiveECSettings().hash; - const h = new sjcl.misc.hmac(tagBits, hash); - - // Always compute derived key using uncompressed point bytes - const encodedPoint = sec1Encode(N, false); - const tokenBits = sjcl.codec.bytes.toBits(token); - const pointBits = sjcl.codec.bytes.toBits(encodedPoint); - - h.update(tokenBits); - h.update(pointBits); - - const keyBytes = sjcl.codec.bytes.fromBits(h.digest()); - return keyBytes; -} - -export function getBytesFromString(str) { - const bits = sjcl.codec.utf8String.toBits(str); - const bytes = sjcl.codec.bytes.fromBits(bits); - return bytes; -} - -export function getBase64FromBytes(bytes) { - const bits = sjcl.codec.bytes.toBits(bytes); - const encoded = sjcl.codec.base64.fromBits(bits); - return encoded; -} - -export function getBase64FromString(str) { - const bits = sjcl.codec.utf8String.toBits(str); - const encoded = sjcl.codec.base64.fromBits(bits); - return encoded; -} - -export function createRequestBinding(key, data) { - // the exact bits of the string "hash_request_binding" - const tagBits = sjcl.codec.utf8String.toBits('hash_request_binding'); - const keyBits = sjcl.codec.bytes.toBits(key); - const hash = getActiveECSettings().hash; - - const h = new sjcl.misc.hmac(keyBits, hash); - h.update(tagBits); - - let dataBits = null; - for (let i = 0; i < data.length; i++) { - dataBits = sjcl.codec.bytes.toBits(data[i]); - h.update(dataBits); - } - - return sjcl.codec.base64.fromBits(h.digest()); -} diff --git a/src/background/voprf.test.ts b/src/background/voprf.test.ts deleted file mode 100644 index 2b9b8c5d..00000000 --- a/src/background/voprf.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defaultECSettings, initECSettings, newRandomPoint } from './voprf'; - -test('randomPoint', () => { - initECSettings(defaultECSettings); - const P = newRandomPoint(); - expect(P).toBeDefined(); -}); From 70ec3f168184dc3054afae7ca68476a615856de5 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 15 Jan 2022 03:57:02 -0800 Subject: [PATCH 07/36] work in progress.. * somewhere between a bug fix and a performance optimization: - pass an instance of the 'voprf' crypto class from provider(s) to the Token constructor - otherwise, each Token initializes its own instance of the 'voprf' crypto class using default parameters --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 2e573cd9..e01f897e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.2.0", + "version": "3.2.1", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 11995ed1..55fa7f42 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.2.0", + "version": "3.2.1", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 77f8eb4d..1052710b 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -59,7 +59,7 @@ export class CloudflareProvider extends Provider { } const tokens: string[] = JSON.parse(stored); - return tokens.map((token) => Token.fromString(token)); + return tokens.map((token) => Token.fromString(token, this.VOPRF)); } private setStoredTokens(tokens: Token[]) { @@ -116,7 +116,7 @@ export class CloudflareProvider extends Provider { url: string, formData: { [key: string]: string[] | string }, ): Promise { - const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token()); + const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token(this.VOPRF)); const issuance = { type: 'Issue', contents: tokens.map((token) => token.getEncodedBlindedPoint()), From 06222cc81ef523a5a971dd1daea32c8e6849e251 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 16 Jan 2022 01:49:37 -0800 Subject: [PATCH 08/36] work in progress.. * hCaptcha provider is added, but not yet functional status: ======= * "handleBeforeRequest()" detects when a captcha is solved on an issuing domain * "issue()" makes a subsequent request for tokens to be signed * the response data does include "signatures", which can be properly parsed: { sigs: string[]; proof: string; version: string = "1.0"; } to do: ====== * the code currently expects that the commitment for the version to be formatted the same as Cloudflare: {H: string; expiry: string; sig: string;} * however, the JSON data file: https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json shapes the data for version HC["1.0"] differently: {H: string; G: string;} * annecdotally, the part that hurts most.. is that this data file includes a version HC["1.01"] that is shaped in a way that is consistent and would work --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.ts | 97 +++--- src/background/providers/hcaptcha.test.ts | 355 ++++++++++++++++++++++ src/background/providers/hcaptcha.ts | 329 +++++++++++++++++++- src/background/providers/provider.ts | 95 ++++++ 6 files changed, 827 insertions(+), 53 deletions(-) create mode 100644 src/background/providers/hcaptcha.test.ts diff --git a/package.json b/package.json index e01f897e..d8d8529a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.2.1", + "version": "3.3.0", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 55fa7f42..6b69e29b 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.2.1", + "version": "3.3.0", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 1052710b..33e5713b 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -1,25 +1,41 @@ import * as voprf from '../crypto/voprf'; -import { Provider, EarnedTokenCookie, Callbacks } from './provider'; +import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams } from './provider'; import { Storage } from '../storage'; import Token from '../token'; import axios from 'axios'; import qs from 'qs'; -const ISSUE_HEADER_NAME = 'cf-chl-bypass'; -const NUMBER_OF_REQUESTED_TOKENS = 30; -const ISSUANCE_BODY_PARAM_NAME = 'blinded-tokens'; +const NUMBER_OF_REQUESTED_TOKENS: number = 30; +const DEFAULT_ISSUING_HOSTNAME: string = 'captcha.website'; +const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; +const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; +const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; -const COMMITMENT_URL = +const COMMITMENT_URL: string = 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; -const QUALIFIED_QUERY_PARAMS = ['__cf_chl_captcha_tk__', '__cf_chl_managed_tk__']; -const QUALIFIED_BODY_PARAMS = ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind']; - -const CHL_BYPASS_SUPPORT = 'cf-chl-bypass'; -const DEFAULT_ISSUING_HOSTNAME = 'captcha.website'; +const ALL_ISSUING_CRITERIA: { + HOSTNAMES: QUALIFIED_HOSTNAMES; + PATHNAMES: QUALIFIED_PATHNAMES; + QUERY_PARAMS: QUALIFIED_PARAMS; + BODY_PARAMS: QUALIFIED_PARAMS; +} = { + HOSTNAMES: { + exact : [DEFAULT_ISSUING_HOSTNAME], + contains: [`.${DEFAULT_ISSUING_HOSTNAME}`], + }, + PATHNAMES: { + }, + QUERY_PARAMS: { + some: ['__cf_chl_captcha_tk__', '__cf_chl_managed_tk__'], + }, + BODY_PARAMS: { + some: ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind'], + } +} -const VERIFICATION_KEY = `-----BEGIN PUBLIC KEY----- +const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExf0AftemLr0YSz5odoj3eJv6SkOF VcH7NNb2xwdEz6Pxm44tvovEl/E+si8hdIDVg1Ys+cbaWwP0jYJW3ygv+Q== -----END PUBLIC KEY-----`; @@ -88,8 +104,8 @@ export class CloudflareProvider extends Provider { } // Check the expiry date. - const expiry = new Date(commitment.expiry); - if (Date.now() >= +expiry) { + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { throw new Error(`Commitments expired in ${expiry.toString()}`); } @@ -129,7 +145,8 @@ export class CloudflareProvider extends Provider { }); const headers = { - 'content-type': 'application/x-www-form-urlencoded', + 'accept': 'application/json', + 'content-type': 'application/x-www-form-urlencoded', [ISSUE_HEADER_NAME]: CloudflareProvider.ID.toString(), }; @@ -147,10 +164,10 @@ export class CloudflareProvider extends Provider { } interface SignaturesParam { - sigs: string[]; + sigs: string[]; version: string; - proof: string; - prng: string; + proof: string; + prng: string; } const data: SignaturesParam = JSON.parse(atob(signatures)); @@ -227,33 +244,44 @@ export class CloudflareProvider extends Provider { handleBeforeRequest( details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { - const url = new URL(details.url); - + // Only issue tokens for POST requests that contain 'application/x-www-form-urlencoded' data. if ( details.requestBody === null || - details.requestBody === undefined || - details.requestBody.formData === undefined + details.requestBody === undefined ) { return; } - const hasQueryParams = QUALIFIED_QUERY_PARAMS.some((param) => { - return url.searchParams.has(param); - }); - const hasBodyParams = QUALIFIED_BODY_PARAMS.some((param) => { - return details.requestBody !== null && param in details.requestBody.formData!; - }); - if (!hasQueryParams || !hasBodyParams) { + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = details.requestBody.formData || {}; + + // Only issue tokens on the issuing website. + if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { + return; + } + + // Only issue tokens when the pathname passes defined criteria. + if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { + return; + } + + // Only issue tokens when querystring parameters pass defined criteria. + if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + return; + } + + // Only issue tokens when POST data parameters pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { return; } const flattenFormData: { [key: string]: string[] | string } = {}; - for (const key in details.requestBody.formData) { - if (details.requestBody.formData[key].length == 1) { - const [value] = details.requestBody.formData[key]; + for (const key in formData) { + if (Array.isArray(formData[key]) && (formData[key].length === 1)) { + const [value] = formData[key]; flattenFormData[key] = value; } else { - flattenFormData[key] = details.requestBody.formData[key]; + flattenFormData[key] = formData[key]; } } @@ -277,9 +305,8 @@ export class CloudflareProvider extends Provider { handleHeadersReceived( details: chrome.webRequest.WebResponseHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - // Don't redeem a token in the issuing website. - const url = new URL(details.url); - if (url.host === DEFAULT_ISSUING_HOSTNAME) { + // Don't redeem a token on the issuing website. + if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { return; } diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts new file mode 100644 index 00000000..e7c2d3ca --- /dev/null +++ b/src/background/providers/hcaptcha.test.ts @@ -0,0 +1,355 @@ +import { jest } from '@jest/globals'; +import { HcaptchaProvider } from './hcaptcha'; +import Token from '../token'; + +export class StorageMock { + store: Map; + + constructor() { + this.store = new Map(); + } + + getItem(key: string): string | null { + return this.store.get(key) ?? null; + } + + setItem(key: string, value: string): void { + this.store.set(key, value); + } +} + +test('getStoredTokens', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.map((token) => token.toString()), + ); +}); + +test('setStoredTokens', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const storedTokens = JSON.parse(storage.store.get('tokens')!); + expect(storedTokens).toEqual(tokens.map((token) => token.toString())); +}); + +test('getBadgeText', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const text = provider['getBadgeText'](); + expect(text).toBe('2'); +}); + +/* + * The issuance involves handleBeforeRequest listener. + * 1. Firstly, the listener check if the request looks like the one that we + * should send an issuance request. + * 2. If it passes the check, the listener returns the cancel command to + * cancel the request. If not, it returns nothing and let the request + * continue. + * 3. At the same time the listener returns, it calls a private method + * "issue" to send an issuance request to the server and the method return + * an array of issued tokens. + * 4. The listener stored the issued tokens in the storage. + * 5. The listener reloads the tab to get the proper web page for the tab.); + */ +describe('issuance', () => { + describe('handleBeforeRequest', () => { + test('valid request', async () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + const issue = jest.fn(async () => { + return tokens; + }); + provider['issue'] = issue; + const url = 'https://www.hcaptcha.com/privacy-pass'; + const details = { + method: 'POST', + url, + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'xmlhttprequest' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestBody: { + formData: { + ['h-captcha-response']: ['body-param'], + }, + }, + }; + const result = await provider.handleBeforeRequest(details); + expect(result).toEqual({ cancel: true }); + + expect(issue.mock.calls.length).toBe(1); + expect(issue).toHaveBeenCalledWith(url, { + ['h-captcha-response']: 'body-param', + }); + + expect(navigateUrl.mock.calls.length).toBe(1); + expect(navigateUrl).toHaveBeenCalledWith('https://www.hcaptcha.com/privacy-pass'); + + // Expect the tokens are added. + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.map((token) => token.toString()), + ); + }); + + /* + * The request is invalid if any of the followings is true: + * 1. It has no url param of any of the followings: + * a. '__cf_chl_captcha_tk__' + * b. '__cf_chl_managed_tk__' + * 2. It has no body param of any of the followings: + * a. 'g-recaptcha-response' + * b. 'h-captcha-response' + * c. 'cf_captcha_kind' + */ + test('invalid request', async () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const issue = jest.fn(async () => []); + provider['issue'] = issue; + const details = { + method: 'GET', + url: 'https://www.hcaptcha.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'xmlhttprequest' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestBody: {}, + }; + const result = await provider.handleBeforeRequest(details); + expect(result).toBeUndefined(); + expect(issue).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); + }); + }); +}); + +/* + * The redemption involves handleHeadersReceived and handleBeforeSendHeaders + * listeners. In handleHeadersReceived listener, + * 1. Firstly, the listener check if the response is the challenge page and + * it supports Privacy Pass redemption. + * 2. If it passes the check, the listener gets a token from the storage to + * redeem. + * 3. The listener sets "redeemInfo" property which includes the request id + * and the mentioned token. The property will be used by + * handleBeforeSendHeaders to redeem the token. + * 4. The listener returns the redirect command so that the browser will + * send the same request again with the token attached. + * + * In handleBeforeSendHeaders, + * 1. The listener will check if the provided request id matches the + * request id in "redeemInfo". If so, it means that the request is from the + * redirect command returned by handleHeadersReceived. If not, it returns + * nothing and let the request continue. + * 2. If it passes the check, the listener attaches the token from + * "redeemInfo" in the "challenge-bypass-token" HTTP header and clears the + * "redeemInfo" property because "redeemInfo" is used already. + */ +describe('redemption', () => { + describe('handleHeadersReceived', () => { + const validDetails = { + url: 'https://non-issuing-subdomain.hcaptcha.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'main_frame' as chrome.webRequest.ResourceType, + timeStamp: 1, + + statusLine: 'HTTP/1.1 403 Forbidden', + statusCode: 403, + responseHeaders: [ + { + name: 'cf-chl-bypass', + value: '2', + }, + ], + method: 'GET', + }; + + test('valid response with tokens', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const details = validDetails; + const result = provider.handleHeadersReceived(details); + expect(result).toEqual({ redirectUrl: details.url }); + // Expect redeemInfo to be set. + const redeemInfo = provider['redeemInfo']; + expect(redeemInfo!.requestId).toEqual(details.requestId); + expect(redeemInfo!.token.toString()).toEqual(tokens[0].toString()); + // Expect a token is used. + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.slice(1).map((token) => token.toString()), + ); + }); + + test('valid response without tokens', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + provider['setStoredTokens']([]); + const details = validDetails; + const result = provider.handleHeadersReceived(details); + expect(result).toBeUndefined(); + }); + + test('no response from an issuing domain', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const details = { + ...validDetails, + url: 'https://www.hcaptcha.com/privacy-pass' + }; + const result = provider.handleHeadersReceived(details); + expect(result).toBeUndefined(); + }); + + /* + * The response is invalid if any of the followings is true: + * 1. The status code is not 403. + * 2. There is no HTTP header of "cf-chl-bypass: 2" + */ + test('no response when the status code is not 403', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const details = { + ...validDetails, + statusLine: 'HTTP/1.1 200 OK', + statusCode: 200 + }; + const result = provider.handleHeadersReceived(details); + expect(result).toBeUndefined(); + }); + + test('no response when there is no HTTP header of "cf-chl-bypass: 2"', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + provider['setStoredTokens'](tokens); + const details = { + ...validDetails, + responseHeaders: undefined + }; + const result = provider.handleHeadersReceived(details); + expect(result).toBeUndefined(); + }); + }); + + describe('handleBeforeSendHeaders', () => { + test('with redeemInfo', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + + const token = Token.fromString( + '{"input":[238,205,51,250,226,251,144,68,170,68,235,25,231,152,125,63,215,10,42,37,65,157,56,22,98,23,129,9,157,179,223,64],"factor":"0x359953995df006ba98bdcf1383a4c75ca79ae41d4e718dcb051832ce65c002bc","blindedPoint":"BCrzbuVf2eSD/5NtR+o09ovo+oRWAwjwopzl7lb+IuOPuj/ctLkdlkeJQUeyjtUbfgJqU4BFNBRz9ln4z3Dk7Us=","unblindedPoint":"BLKf1op+oq4FcbNdP5vygTkGO3WWLHD6oXCCZDfaFyuFlruih49BStHm6QxtZZAqgCR9i6SsO6VP69hHnfBDNeg=","signed":{"blindedPoint":"BKEnbsQSwnHCxEv4ppp6XuqLV60FiQpF8YWvodQHdnmFHv7CKyWHqBLBW8fJ2uuV+uLxl99+VRYPxr8Q8E7i2Iw=","unblindedPoint":"BA8G3dHM554FzDiOtEsSBu0XYW8p5vA2OIEvnYQcJlRGHTiq2N6j3BKUbiI7I6fAy2vsOrwhrLGHOD+q7YxO+UM="}}', + ); + const redeemInfo = { + requestId: 'xxx', + token, + }; + provider['redeemInfo'] = redeemInfo; + const details = { + method: 'GET', + url: 'https://www.hcaptcha.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'main_frame' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestHeaders: [], + }; + const result = provider.handleBeforeSendHeaders(details); + expect(result).toEqual({ + requestHeaders: [ + { + name: 'challenge-bypass-token', + value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJyeXRSRExLN3J2THVhd09XZkJ0RXJTclVuUWpIaGpLbkNKK3RqQnhQSFYwPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', + }, + ], + }); + const newRedeemInfo = provider['redeemInfo']; + expect(newRedeemInfo).toBeNull(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith('0'); + }); + + test('without redeemInfo', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + + const details = { + method: 'GET', + url: 'https://www.hcaptcha.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'main_frame' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestHeaders: [], + }; + const result = provider.handleBeforeSendHeaders(details); + expect(result).toBeUndefined(); + expect(updateIcon).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 7a5f7b2e..77a1fb06 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -1,27 +1,201 @@ -import { Provider, EarnedTokenCookie, Callbacks } from './provider'; +import * as voprf from '../crypto/voprf'; + +import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams } from './provider'; import { Storage } from '../storage'; +import Token from '../token'; +import axios from 'axios'; +import qs from 'qs'; + +const NUMBER_OF_REQUESTED_TOKENS: number = 5; +const DEFAULT_ISSUING_HOSTNAME: string = 'hcaptcha.com'; +const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; +const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; +const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; + +const COMMITMENT_URL: string = + 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; + +const ALL_ISSUING_CRITERIA: { + HOSTNAMES: QUALIFIED_HOSTNAMES; + PATHNAMES: QUALIFIED_PATHNAMES; + QUERY_PARAMS: QUALIFIED_PARAMS; + BODY_PARAMS: QUALIFIED_PARAMS; +} = { + HOSTNAMES: { + exact : [DEFAULT_ISSUING_HOSTNAME], + contains: [`.${DEFAULT_ISSUING_HOSTNAME}`], + }, + PATHNAMES: { + contains: ['/checkcaptcha'], + }, + QUERY_PARAMS: { + some: ['s=00000000-0000-0000-0000-000000000000'], + }, + BODY_PARAMS: { + } +} + +const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4OifvSTxGcy3T/yac6LVugArFb89 +wvqGivp0/54wgeyWkvUZiUdlbIQF7BuGeO9C4sx4nHkpAgRfvd8jdBGz9g== +-----END PUBLIC KEY-----`; + +interface RedeemInfo { + requestId: string; + token: Token; +} export class HcaptchaProvider extends Provider { static readonly ID: number = 2; static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie = { - url: 'https://www.hcaptcha.com/privacy-pass', - domain: '.hcaptcha.com', + url: `https://www.${DEFAULT_ISSUING_HOSTNAME}/privacy-pass`, + domain: `.${DEFAULT_ISSUING_HOSTNAME}`, name: 'hc_clearance' }; - private callbacks: Callbacks; -// private storage: Storage; + private VOPRF: voprf.VOPRF; + private callbacks: Callbacks; + private storage: Storage; + private redeemInfo: RedeemInfo | null; constructor(storage: Storage, callbacks: Callbacks) { super(storage, callbacks); - this.callbacks = callbacks; -// this.storage = storage; + this.VOPRF = new voprf.VOPRF(voprf.defaultECSettings); + this.callbacks = callbacks; + this.storage = storage; + this.redeemInfo = null; + } + + private getStoredTokens(): Token[] { + const stored = this.storage.getItem(Provider.TOKEN_STORE_KEY); + if (stored === null) { + return []; + } + + const tokens: string[] = JSON.parse(stored); + return tokens.map((token) => Token.fromString(token, this.VOPRF)); + } + + private setStoredTokens(tokens: Token[]) { + this.storage.setItem( + Provider.TOKEN_STORE_KEY, + JSON.stringify(tokens.map((token) => token.toString())), + ); + } + + private async getCommitment(version: string): Promise<{ G: string; H: string }> { + const keyPrefix = 'commitment-'; + const cached = this.storage.getItem(`${keyPrefix}${version}`); + if (cached !== null) { + return JSON.parse(cached); + } + + interface Response { + HC: { [version: string]: { H: string; expiry: string; sig: string } }; + } + + // Download the commitment + const { data } = await axios.get(COMMITMENT_URL); + const commitment = data.HC[version]; + if (commitment === undefined) { + throw new Error(`No commitment for the version ${version} is found`); + } + + // Check the expiry date. + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { + throw new Error(`Commitments expired in ${expiry.toString()}`); + } + + // This will throw an error on a bad signature. + this.VOPRF.verifyConfiguration( + VERIFICATION_KEY, + { + H: commitment.H, + expiry: commitment.expiry, + }, + commitment.sig, + ); + + // Cache. + const item = { + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), + H: commitment.H, + }; + this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); + return item; + } + + private async issue( + url: string, + formData: { [key: string]: string[] | string }, + ): Promise { + const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token(this.VOPRF)); + const issuance = { + type: 'Issue', + contents: tokens.map((token) => token.getEncodedBlindedPoint()), + }; + const param = btoa(JSON.stringify(issuance)); + + const body = qs.stringify({ + ...formData, + [ISSUANCE_BODY_PARAM_NAME]: param, + 'captcha-bypass': true, + }); + + const headers = { + 'accept': 'application/json', + 'content-type': 'application/x-www-form-urlencoded', + [ISSUE_HEADER_NAME]: HcaptchaProvider.ID.toString(), + }; + + const response = await axios.post(url, body, { + headers, + responseType: 'text', + }); + + const { signatures } = qs.parse(response.data); + if (signatures === undefined) { + throw new Error('There is no signatures parameter in the issuance response.'); + } + if (typeof signatures !== 'string') { + throw new Error('The signatures parameter in the issuance response is not a string.'); + } + + interface SignaturesParam { + sigs: string[]; + version: string; + proof: string; + prng: string; + } + + const data: SignaturesParam = JSON.parse(atob(signatures)); + const returned = this.VOPRF.getCurvePoints(data.sigs); + + const commitment = await this.getCommitment(data.version); + + const result = this.VOPRF.verifyProof( + data.proof, + tokens.map((token) => token.toLegacy()), + returned, + commitment, + data.prng, + ); + if (!result) { + throw new Error('DLEQ proof is invalid.'); + } + + tokens.forEach((token, index) => { + token.setSignedPoint(returned.points[index]); + }); + + return tokens; } private getBadgeText(): string { - return 'N/A'; + return this.getStoredTokens().length.toString(); } forceUpdateIcon(): void { @@ -32,19 +206,142 @@ export class HcaptchaProvider extends Provider { this.callbacks.updateIcon(this.getBadgeText()); } - handleBeforeRequest( - _details: chrome.webRequest.WebRequestBodyDetails, + handleBeforeSendHeaders( + details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - return; + if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { + return; + } + + const url = new URL(details.url); + + const token = this.redeemInfo!.token; + // Clear the redeem info to indicate that we are already redeeming the token. + this.redeemInfo = null; + + const key = token.getMacKey(); + const binding = this.VOPRF.createRequestBinding(key, [ + voprf.getBytesFromString(url.hostname), + voprf.getBytesFromString(details.method + ' ' + url.pathname), + ]); + + const contents = [ + voprf.getBase64FromBytes(token.getInput()), + binding, + voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), + ]; + const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); + + const headers = details.requestHeaders ?? []; + headers.push({ name: 'challenge-bypass-token', value: redemption }); + + this.callbacks.updateIcon(this.getBadgeText()); + + return { + requestHeaders: headers, + }; } - handleBeforeSendHeaders( - _details: chrome.webRequest.WebRequestHeadersDetails, + + handleBeforeRequest( + details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { - return; + // Only issue tokens for POST requests that contain 'application/x-www-form-urlencoded' data. + if ( + details.requestBody === null || + details.requestBody === undefined + ) { + return; + } + + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = details.requestBody.formData || {}; + + // Only issue tokens on the issuing website. + if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { + return; + } + + // Only issue tokens when the pathname passes defined criteria. + if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { + return; + } + + // Only issue tokens when querystring parameters pass defined criteria. + if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + return; + } + + // Only issue tokens when POST data parameters pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { + return; + } + + const flattenFormData: { [key: string]: string[] | string } = {}; + for (const key in formData) { + if (Array.isArray(formData[key]) && (formData[key].length === 1)) { + const [value] = formData[key]; + flattenFormData[key] = value; + } else { + flattenFormData[key] = formData[key]; + } + } + + // delay the request to issue tokens until next tick of the event loop + setTimeout( + async () => { + // Issue tokens. + const tokens = await this.issue(details.url, flattenFormData); + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + + this.callbacks.navigateUrl(`${url.origin}${url.pathname}`); + }, + 0 + ); + + // do NOT cancel the original captcha solve request + return { cancel: false }; } + handleHeadersReceived( - _details: chrome.webRequest.WebResponseHeadersDetails, + details: chrome.webRequest.WebResponseHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - return; + // Don't redeem a token on the issuing website. + if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { + return; + } + + // Check if it's the response of the request that we should insert a token. + if (details.statusCode !== 403 || details.responseHeaders === undefined) { + return; + } + const hasSupportHeader = details.responseHeaders.some((header) => { + return ( + header.name.toLowerCase() === CHL_BYPASS_SUPPORT && + header.value !== undefined && + parseInt(header.value, 10) === HcaptchaProvider.ID + ); + }); + if (!hasSupportHeader) { + return; + } + + // Let's try to redeem. + + // Get one token. + const tokens = this.getStoredTokens(); + const token = tokens.shift(); + this.setStoredTokens(tokens); + + if (token === undefined) { + return; + } + + this.redeemInfo = { requestId: details.requestId, token }; + // Redirect to resend the request attached with the token. + return { + redirectUrl: details.url, + }; } } diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts index c38ec6d7..20970503 100644 --- a/src/background/providers/provider.ts +++ b/src/background/providers/provider.ts @@ -31,3 +31,98 @@ export abstract class Provider { details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void; } + +// ----------------------------------------------------------------------------- +// static methods + +interface QUALIFIED_STRING { + exact?: string[]; + contains?: string[]; +} + +export type QUALIFIED_HOSTNAMES = QUALIFIED_STRING; +export type QUALIFIED_PATHNAMES = QUALIFIED_STRING; + +export interface QUALIFIED_PARAMS { + some?: string[]; + every?: string[]; +} + +function isQualifiedStringFound(haystack: QUALIFIED_STRING | void, needle: string, result_empty_haystack: boolean = true): boolean { + let empty = true; + let found = false; + + if (haystack instanceof Object) { + if (!found && Array.isArray(haystack.exact) && haystack.exact.length) { + empty = false; + found = (haystack.exact.indexOf(needle) >= 0); + } + if (!found && Array.isArray(haystack.contains) && haystack.contains.length) { + empty = false; + found = haystack.contains.some(part => (needle.indexOf(part) >= 0)); + } + } + + return empty ? result_empty_haystack : found; +} + +export function isIssuingHostname(hostnames: QUALIFIED_HOSTNAMES | void, url: URL): boolean { + const hostname = url.host.toLowerCase(); + return isQualifiedStringFound(hostnames, hostname, false); +} + +export function isQualifiedPathname(pathnames: QUALIFIED_PATHNAMES | void, url: URL): boolean { + const pathname = url.pathname.toLowerCase(); + return isQualifiedStringFound(pathnames, pathname, true); +} + +function areQualifiedParamsFound(params: QUALIFIED_PARAMS | void, test: (param: string) => boolean, result_empty_haystack: boolean = true): boolean { + let empty = true; + let found = false; + + if (params instanceof Object) { + if (!found && Array.isArray(params.some) && params.some.length) { + empty = false; + found = params.some.some(test); + } + if (!found && Array.isArray(params.every) && params.every.length) { + empty = false; + found = params.every.every(test); + } + } + + return empty ? result_empty_haystack : found; +} + +function isQualifiedQueryParam(url: URL, param: string): boolean { + const [param_name, param_value] = param.split('=', 2); + if (!url.searchParams.has(param_name)) return false; + if (param_value && (url.searchParams.get(param_name) !== param_value)) return false; + return true; +} + +export function areQualifiedQueryParams(params: QUALIFIED_PARAMS | void, url: URL): boolean { + const test: (param: string) => boolean = isQualifiedQueryParam.bind(null, url); + return areQualifiedParamsFound(params, test, true); +} + +function isQualifiedBodyFormParam(formData: { [key: string]: string[] | string } | void, param: string): boolean { + if (!(formData instanceof Object)) return false; + + const [param_name, param_value] = param.split('=', 2); + if (!(param_name in formData)) return false; + if (param_value) { + if (Array.isArray(formData[param_name])) { + if (formData[param_name].indexOf(param_value) === -1) return false; + } + else { + if (formData[param_name] !== param_value) return false; + } + } + return true; +} + +export function areQualifiedBodyFormParams(params: QUALIFIED_PARAMS | void, formData: { [key: string]: string[] | string } | void): boolean { + const test: (param: string) => boolean = isQualifiedBodyFormParam.bind(null, formData); + return areQualifiedParamsFound(params, test, true); +} From a4e23fbc8373d6843052f8ee0b747b420b5037e1 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 16 Jan 2022 02:56:57 -0800 Subject: [PATCH 09/36] work in progress.. * hCaptcha provider is added, but not yet functional status: ======= * "handleBeforeRequest()" detects when a captcha is solved on an issuing domain * "issue()" makes a subsequent request for tokens to be signed * the response data does include "signatures", which can be properly parsed: { sigs: string[]; proof: string; version: string = "1.0"; prng?: string = undefined; } * the version "1.0" commitment is shaped: {H: string; G: string;} which, unlike Cloudflare, does not require verification * since "prng" is not defined, it should default to "shake", as was the methodology used by the v2 extension to do: ====== * the "shake" prng has a missing dependency: Buffer --- src/background/providers/cloudflare.ts | 48 ++++++++++++++++---------- src/background/providers/hcaptcha.ts | 48 ++++++++++++++++---------- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 33e5713b..ce87e86e 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -93,7 +93,7 @@ export class CloudflareProvider extends Provider { } interface Response { - CF: { [version: string]: { H: string; expiry: string; sig: string } }; + CF: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; } // Download the commitment @@ -103,28 +103,38 @@ export class CloudflareProvider extends Provider { throw new Error(`No commitment for the version ${version} is found`); } - // Check the expiry date. - const expiry: number = (new Date(commitment.expiry)).getTime(); - if (Date.now() >= expiry) { - throw new Error(`Commitments expired in ${expiry.toString()}`); + let item: { G: string; H: string }; + + // Does the commitment require verification? + if ('G' in commitment) { + item = commitment; } + else { + // Check the expiry date. + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { + throw new Error(`Commitments expired in ${expiry.toString()}`); + } + + // This will throw an error on a bad signature. + this.VOPRF.verifyConfiguration( + VERIFICATION_KEY, + { + H: commitment.H, + expiry: commitment.expiry, + }, + commitment.sig, + ); - // This will throw an error on a bad signature. - this.VOPRF.verifyConfiguration( - VERIFICATION_KEY, - { + item = { + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), H: commitment.H, - expiry: commitment.expiry, - }, - commitment.sig, - ); + }; + } // Cache. - const item = { - G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), - H: commitment.H, - }; this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); + return item; } @@ -167,7 +177,7 @@ export class CloudflareProvider extends Provider { sigs: string[]; version: string; proof: string; - prng: string; + prng?: string; } const data: SignaturesParam = JSON.parse(atob(signatures)); @@ -180,7 +190,7 @@ export class CloudflareProvider extends Provider { tokens.map((token) => token.toLegacy()), returned, commitment, - data.prng, + data.prng || 'shake', ); if (!result) { throw new Error('DLEQ proof is invalid.'); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 77a1fb06..073a4db5 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -93,7 +93,7 @@ export class HcaptchaProvider extends Provider { } interface Response { - HC: { [version: string]: { H: string; expiry: string; sig: string } }; + HC: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; } // Download the commitment @@ -103,28 +103,38 @@ export class HcaptchaProvider extends Provider { throw new Error(`No commitment for the version ${version} is found`); } - // Check the expiry date. - const expiry: number = (new Date(commitment.expiry)).getTime(); - if (Date.now() >= expiry) { - throw new Error(`Commitments expired in ${expiry.toString()}`); + let item: { G: string; H: string }; + + // Does the commitment require verification? + if ('G' in commitment) { + item = commitment; } + else { + // Check the expiry date. + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { + throw new Error(`Commitments expired in ${expiry.toString()}`); + } - // This will throw an error on a bad signature. - this.VOPRF.verifyConfiguration( - VERIFICATION_KEY, - { + // This will throw an error on a bad signature. + this.VOPRF.verifyConfiguration( + VERIFICATION_KEY, + { + H: commitment.H, + expiry: commitment.expiry, + }, + commitment.sig, + ); + + item = { + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), H: commitment.H, - expiry: commitment.expiry, - }, - commitment.sig, - ); + }; + } // Cache. - const item = { - G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), - H: commitment.H, - }; this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); + return item; } @@ -168,7 +178,7 @@ export class HcaptchaProvider extends Provider { sigs: string[]; version: string; proof: string; - prng: string; + prng?: string; } const data: SignaturesParam = JSON.parse(atob(signatures)); @@ -181,7 +191,7 @@ export class HcaptchaProvider extends Provider { tokens.map((token) => token.toLegacy()), returned, commitment, - data.prng, + data.prng || 'shake', ); if (!result) { throw new Error('DLEQ proof is invalid.'); From 00cba76e3db7e4cdb8e5ab3e7304f71bbb9b1d62 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 16 Jan 2022 05:09:21 -0800 Subject: [PATCH 10/36] v3.3.0 w/ Cloudflare and hCaptcha providers --- src/background/listeners/messageListener.ts | 2 +- src/background/providers/cloudflare.ts | 32 ++++++++++--------- src/background/providers/hcaptcha.ts | 9 +++--- src/popup/components/HcaptchaButton/index.tsx | 11 ++++++- webpack.config.js | 12 ++++--- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/background/listeners/messageListener.ts b/src/background/listeners/messageListener.ts index 3d10fb7c..b22f6543 100644 --- a/src/background/listeners/messageListener.ts +++ b/src/background/listeners/messageListener.ts @@ -30,7 +30,7 @@ export function handleReceivedMessage(request: any, _sender: chrome.runtime.Mess try { const tokensArray: string[] = JSON.parse(tokensJSON); - response[`${provider.ID}`] = Number(tokensArray.length); + response[ provider.ID.toString() ] = Number(tokensArray.length); } catch (error: any) {} } diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index ce87e86e..bb9ad287 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -83,6 +83,8 @@ export class CloudflareProvider extends Provider { Provider.TOKEN_STORE_KEY, JSON.stringify(tokens.map((token) => token.toString())), ); + + this.forceUpdateIcon(); } private async getCommitment(version: string): Promise<{ G: string; H: string }> { @@ -212,7 +214,7 @@ export class CloudflareProvider extends Provider { } handleActivated(): void { - this.callbacks.updateIcon(this.getBadgeText()); + this.forceUpdateIcon(); } handleBeforeSendHeaders( @@ -244,8 +246,6 @@ export class CloudflareProvider extends Provider { const headers = details.requestHeaders ?? []; headers.push({ name: 'challenge-bypass-token', value: redemption }); - this.callbacks.updateIcon(this.getBadgeText()); - return { requestHeaders: headers, }; @@ -295,20 +295,22 @@ export class CloudflareProvider extends Provider { } } - (async () => { - // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); + // delay the request to issue tokens until next tick of the event loop + setTimeout( + async () => { + // Issue tokens. + const tokens = await this.issue(details.url, flattenFormData); + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); - this.callbacks.navigateUrl(`${url.origin}${url.pathname}`); - })(); + this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + }, + 0 + ); - // TODO I tried to use redirectUrl with data URL or text/html and text/plain but it didn't work, so I continue - // cancelling the request. However, it seems that we can use image/* except image/svg+html. Let's figure how to - // use image data URL later. - // https://blog.mozilla.org/security/2017/11/27/blocking-top-level-navigations-data-urls-firefox-59/ + // safe to cancel return { cancel: true }; } diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 073a4db5..8cec2413 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -83,6 +83,8 @@ export class HcaptchaProvider extends Provider { Provider.TOKEN_STORE_KEY, JSON.stringify(tokens.map((token) => token.toString())), ); + + this.forceUpdateIcon(); } private async getCommitment(version: string): Promise<{ G: string; H: string }> { @@ -213,7 +215,7 @@ export class HcaptchaProvider extends Provider { } handleActivated(): void { - this.callbacks.updateIcon(this.getBadgeText()); + this.forceUpdateIcon(); } handleBeforeSendHeaders( @@ -245,8 +247,6 @@ export class HcaptchaProvider extends Provider { const headers = details.requestHeaders ?? []; headers.push({ name: 'challenge-bypass-token', value: redemption }); - this.callbacks.updateIcon(this.getBadgeText()); - return { requestHeaders: headers, }; @@ -301,11 +301,12 @@ export class HcaptchaProvider extends Provider { async () => { // Issue tokens. const tokens = await this.issue(details.url, flattenFormData); + // Store tokens. const cached = this.getStoredTokens(); this.setStoredTokens(cached.concat(tokens)); - this.callbacks.navigateUrl(`${url.origin}${url.pathname}`); + this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); }, 0 ); diff --git a/src/popup/components/HcaptchaButton/index.tsx b/src/popup/components/HcaptchaButton/index.tsx index f1879fd8..41b99b32 100644 --- a/src/popup/components/HcaptchaButton/index.tsx +++ b/src/popup/components/HcaptchaButton/index.tsx @@ -1,14 +1,23 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import { PassButton } from '@popup/components/PassButton'; +const providerID: string = '2'; + export function HcaptchaButton(): JSX.Element { + const tokensCount: number = useSelector((state: {[key: string]: number} | void): number => { + return ((state instanceof Object) && (typeof state[providerID] === 'number')) + ? Number(state[providerID]) + : 0; + }); + const openHomePage = () => { chrome.tabs.create({ url: 'https://www.hcaptcha.com/privacy-pass' }); }; return ( - + {chrome.i18n.getMessage('providerNameHcaptcha')} ); diff --git a/webpack.config.js b/webpack.config.js index 445a39a6..24574925 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,12 +1,10 @@ +import webpack from 'webpack'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; import path from 'path'; -// import buffer from "buffer"; -// import streamBrowserify from "stream-browserify"; - const __dirname = (() => { const filepath_uri = import.meta.url; const prefix = `file:${path.sep === '/' ? '' : path.sep}`; @@ -49,10 +47,14 @@ const background = { resolve: { extensions: ['.tsx', '.ts', '.js'], fallback: { - // 'buffer': buffer, - // 'stream': streamBrowserify, + buffer: 'buffer', }, }, + plugins: [ + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], + }), + ], }; const popup = { From 6fe4c6c159cdf32c7231d3083f0c1a546920ccd5 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 16 Jan 2022 21:45:24 -0800 Subject: [PATCH 11/36] update jest configs for testing and fix all tests to pass --- jest.setup.ts | 6 ++- package.json | 2 +- src/background/crypto/voprf.js | 8 +-- src/background/providers/cloudflare.test.ts | 40 +++++++++++---- src/background/providers/cloudflare.ts | 2 +- src/background/providers/hcaptcha.test.ts | 56 +++++++++++++-------- tsconfig.json | 5 +- 7 files changed, 81 insertions(+), 38 deletions(-) diff --git a/jest.setup.ts b/jest.setup.ts index 5e0b2c0f..3b373b7d 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1,6 +1,10 @@ // Mocking crypto with Node webcrypto API. + +// Requires Node v15.0+ +// https://nodejs.org/api/crypto.html#cryptowebcrypto + import { webcrypto } from 'crypto'; if (typeof crypto === 'undefined') { - global.crypto = (webcrypto as unknown) as Crypto + global.crypto = (webcrypto as unknown) as Crypto; } diff --git a/package.json b/package.json index d8d8529a..056d996e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "prebuild": "npm run sjcl", "build": "webpack", "pretest": "npm run sjcl", - "test": "node --experimental-vm-modules node_modules/.bin/jest", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "lint": "eslint ./src/**/*.{ts,tsx}", "clean": "rimraf lib && rimraf PrivacyPass && rimraf PrivacyPass.crx && rimraf PrivacyPass.xpi" }, diff --git a/src/background/crypto/voprf.js b/src/background/crypto/voprf.js index 1aee9228..3a97818e 100644 --- a/src/background/crypto/voprf.js +++ b/src/background/crypto/voprf.js @@ -17,9 +17,11 @@ import { createShake256 } from './pseudorandom-number-generators/shake.ts'; let PEM; let ASN1; +let crypto; if (typeof window !== 'undefined') { - PEM = window.PEM; - ASN1 = window.ASN1; + PEM = window.PEM; + ASN1 = window.ASN1; + crypto = window.crypto; } const BATCH_PROOF_PREFIX = 'batch-proof='; @@ -437,7 +439,7 @@ export class VOPRF { * @return {sjcl.ecc.point} */ newRandomPoint() { - const random = window.crypto.getRandomValues(new Int32Array(8)); + const random = crypto.getRandomValues(new Int32Array(8)); // Choose hash-to-curve method const point = this.h2Curve(random, this.getActiveECSettings()); diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 94b3de00..b925a295 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -2,6 +2,16 @@ import { jest } from '@jest/globals'; import { CloudflareProvider } from './cloudflare'; import Token from '../token'; +beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); +}); + +afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); +}); + export class StorageMock { store: Map; @@ -94,26 +104,35 @@ describe('issuance', () => { timeStamp: 1, requestBody: { formData: { - ['h-captcha-response']: ['body-param'], + 'h-captcha-response': ['body-param'], }, }, }; - const result = await provider.handleBeforeRequest(details); + const result = provider.handleBeforeRequest(details); expect(result).toEqual({ cancel: true }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + + jest.runAllTimers(); + await Promise.resolve(); + expect(issue.mock.calls.length).toBe(1); expect(issue).toHaveBeenCalledWith(url, { - ['h-captcha-response']: 'body-param', + 'h-captcha-response': 'body-param', }); - expect(navigateUrl.mock.calls.length).toBe(1); - expect(navigateUrl).toHaveBeenCalledWith('https://captcha.website/'); - // Expect the tokens are added. const storedTokens = provider['getStoredTokens'](); expect(storedTokens.map((token) => token.toString())).toEqual( tokens.map((token) => token.toString()), ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + expect(navigateUrl.mock.calls.length).toBe(1); + expect(navigateUrl).toHaveBeenCalledWith('https://captcha.website/'); }); /* @@ -145,9 +164,12 @@ describe('issuance', () => { timeStamp: 1, requestBody: {}, }; - const result = await provider.handleBeforeRequest(details); + const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); + + expect(setTimeout).not.toHaveBeenCalled(); expect(issue).not.toHaveBeenCalled(); + expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); }); }); @@ -314,9 +336,7 @@ describe('redemption', () => { }); const newRedeemInfo = provider['redeemInfo']; expect(newRedeemInfo).toBeNull(); - - expect(updateIcon.mock.calls.length).toBe(1); - expect(updateIcon).toHaveBeenCalledWith('0'); + expect(updateIcon).not.toHaveBeenCalled(); }); test('without redeemInfo', () => { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index bb9ad287..0780f9ca 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -49,7 +49,7 @@ export class CloudflareProvider extends Provider { static readonly ID: number = 1; static readonly EARNED_TOKEN_COOKIE: EarnedTokenCookie = { - url: `https://${DEFAULT_ISSUING_HOSTNAME}`, + url: `https://${DEFAULT_ISSUING_HOSTNAME}/`, domain: `.${DEFAULT_ISSUING_HOSTNAME}`, name: 'cf_clearance' }; diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index e7c2d3ca..089058c1 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -2,6 +2,16 @@ import { jest } from '@jest/globals'; import { HcaptchaProvider } from './hcaptcha'; import Token from '../token'; +beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); +}); + +afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); +}); + export class StorageMock { store: Map; @@ -61,8 +71,8 @@ test('getBadgeText', () => { * 1. Firstly, the listener check if the request looks like the one that we * should send an issuance request. * 2. If it passes the check, the listener returns the cancel command to - * cancel the request. If not, it returns nothing and let the request - * continue. + * explicitly prevent cancelling the request. + * If not, it returns nothing and let the request continue. * 3. At the same time the listener returns, it calls a private method * "issue" to send an issuance request to the server and the method return * an array of issued tokens. @@ -82,7 +92,7 @@ describe('issuance', () => { return tokens; }); provider['issue'] = issue; - const url = 'https://www.hcaptcha.com/privacy-pass'; + const url = 'https://www.hcaptcha.com/checkcaptcha/?s=00000000-0000-0000-0000-000000000000'; const details = { method: 'POST', url, @@ -93,27 +103,32 @@ describe('issuance', () => { type: 'xmlhttprequest' as chrome.webRequest.ResourceType, timeStamp: 1, requestBody: { - formData: { - ['h-captcha-response']: ['body-param'], - }, + formData: {}, }, }; - const result = await provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: true }); + const result = provider.handleBeforeRequest(details); + expect(result).toEqual({ cancel: false }); - expect(issue.mock.calls.length).toBe(1); - expect(issue).toHaveBeenCalledWith(url, { - ['h-captcha-response']: 'body-param', - }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - expect(navigateUrl.mock.calls.length).toBe(1); - expect(navigateUrl).toHaveBeenCalledWith('https://www.hcaptcha.com/privacy-pass'); + jest.runAllTimers(); + await Promise.resolve(); + + expect(issue.mock.calls.length).toBe(1); + expect(issue).toHaveBeenCalledWith(url, {}); // Expect the tokens are added. const storedTokens = provider['getStoredTokens'](); expect(storedTokens.map((token) => token.toString())).toEqual( tokens.map((token) => token.toString()), ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + expect(navigateUrl.mock.calls.length).toBe(1); + expect(navigateUrl).toHaveBeenCalledWith('https://www.hcaptcha.com/privacy-pass'); }); /* @@ -145,9 +160,12 @@ describe('issuance', () => { timeStamp: 1, requestBody: {}, }; - const result = await provider.handleBeforeRequest(details); + const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); + + expect(setTimeout).not.toHaveBeenCalled(); expect(issue).not.toHaveBeenCalled(); + expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); }); }); @@ -178,7 +196,7 @@ describe('issuance', () => { describe('redemption', () => { describe('handleHeadersReceived', () => { const validDetails = { - url: 'https://non-issuing-subdomain.hcaptcha.com/', + url: 'https://non-issuing-domain.example.com/', requestId: 'xxx', frameId: 1, parentFrameId: 1, @@ -318,15 +336,13 @@ describe('redemption', () => { requestHeaders: [ { name: 'challenge-bypass-token', - value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJyeXRSRExLN3J2THVhd09XZkJ0RXJTclVuUWpIaGpLbkNKK3RqQnhQSFYwPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', + value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJxNmhOM2krakRmQXlpOW1MdjFaSE04alNRSng4SWZKZThWYUIvQU9UYm9FPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', }, ], }); const newRedeemInfo = provider['redeemInfo']; expect(newRedeemInfo).toBeNull(); - - expect(updateIcon.mock.calls.length).toBe(1); - expect(updateIcon).toHaveBeenCalledWith('0'); + expect(updateIcon).not.toHaveBeenCalled(); }); test('without redeemInfo', () => { diff --git a/tsconfig.json b/tsconfig.json index 209346bc..412ec5dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { - "target": "es2020", - "module": "es2020", + "lib": ["ES2020", "dom"], + "module": "ES2020", + "target": "ES2020", "declaration": true, "declarationMap": true, "sourceMap": true, From 578a97bb776194f5cfdb02f27d11a58d75568567 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Mon, 17 Jan 2022 01:39:52 -0800 Subject: [PATCH 12/36] v3.3.1 w/ minified ES5 --- package.json | 2 +- public/manifest.json | 2 +- webpack.config.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 056d996e..f0e97c95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.3.0", + "version": "3.3.1", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 6b69e29b..7909b6e7 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.3.0", + "version": "3.3.1", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/webpack.config.js b/webpack.config.js index 24574925..dd1d5dcf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -30,8 +30,9 @@ const common = { }, context: __dirname, mode: 'production', + target: ['web', 'es5'], optimization: { - minimize: false, + minimize: true, }, }; From 929cabdee2f98f1a01a2817213592f5177220f44 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Tue, 18 Jan 2022 11:25:10 -0800 Subject: [PATCH 13/36] v3.3.2 w/ minified ES5 fix: transpile ES6 node modules: 'buffer' and 'keccak' --- package-lock.json | 3909 +++++++++++++++++----------------- package.json | 5 +- public/manifest.json | 2 +- src/background/tsconfig.json | 1 - src/popup/tsconfig.json | 1 - tsconfig.json | 19 +- webpack.config.js | 21 +- 7 files changed, 1962 insertions(+), 1996 deletions(-) diff --git a/package-lock.json b/package-lock.json index f94676e1..006e036b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "privacy-pass", - "version": "3.2.0", + "version": "3.3.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "privacy-pass", - "version": "3.2.0", + "version": "3.3.2", "license": "BSD-3-Clause", "dependencies": { "asn1-parser": "^1.1.8", @@ -18,8 +18,7 @@ "react-dom": "^17.0.2", "react-redux": "^7.2.5", "redux": "^4.1.1", - "sjcl": "^1.0.8", - "stream-browserify": "^3.0.0" + "sjcl": "^1.0.8" }, "devDependencies": { "@types/chrome": "^0.0.159", @@ -62,29 +61,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", + "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", + "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.7", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -101,12 +100,12 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -131,12 +130,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", + "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.6", + "@babel/types": "^7.16.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -154,14 +153,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", "semver": "^6.3.0" }, "engines": { @@ -180,186 +179,159 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", + "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "dependencies": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", + "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", "dev": true, "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -439,9 +411,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", + "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -598,12 +570,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -613,9 +585,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", + "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -624,44 +596,45 @@ } }, "node_modules/@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", + "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.16.8", + "@babel/types": "^7.16.8", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -670,12 +643,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -691,12 +664,12 @@ } }, "node_modules/@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -710,9 +683,9 @@ "dev": true }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", - "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", + "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true, "engines": { "node": ">=10.0.0" @@ -762,9 +735,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -802,16 +775,16 @@ } }, "node_modules/@jest/console": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.3.1.tgz", - "integrity": "sha512-RkFNWmv0iui+qsOr/29q9dyfKTTT5DCuP31kUwg7rmOKPT/ozLeGLKJKVIiOfbiKyleUZKIrHwhmiZWVe8IMdw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", + "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.3.1", - "jest-util": "^27.3.1", + "jest-message-util": "^27.4.6", + "jest-util": "^27.4.2", "slash": "^3.0.0" }, "engines": { @@ -819,35 +792,35 @@ } }, "node_modules/@jest/core": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.3.1.tgz", - "integrity": "sha512-DMNE90RR5QKx0EA+wqe3/TNEwiRpOkhshKNxtLxd4rt3IZpCt+RSL+FoJsGeblRZmqdK4upHA/mKKGPPRAifhg==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", + "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", "dev": true, "dependencies": { - "@jest/console": "^27.3.1", - "@jest/reporters": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/reporters": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.3.0", - "jest-config": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-resolve-dependencies": "^27.3.1", - "jest-runner": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", - "jest-watcher": "^27.3.1", + "jest-changed-files": "^27.4.2", + "jest-config": "^27.4.7", + "jest-haste-map": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-resolve-dependencies": "^27.4.6", + "jest-runner": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", + "jest-watcher": "^27.4.6", "micromatch": "^4.0.4", "rimraf": "^3.0.0", "slash": "^3.0.0", @@ -866,62 +839,62 @@ } }, "node_modules/@jest/environment": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.3.1.tgz", - "integrity": "sha512-BCKCj4mOVLme6Tanoyc9k0ultp3pnmuyHw73UHRPeeZxirsU/7E3HC4le/VDb/SMzE1JcPnto+XBKFOcoiJzVw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", + "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0" + "jest-mock": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.3.1.tgz", - "integrity": "sha512-M3ZFgwwlqJtWZ+QkBG5NmC23A9w+A6ZxNsO5nJxJsKYt4yguBd3i8TpjQz5NfCX91nEve1KqD9RA2Q+Q1uWqoA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", + "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^27.3.1", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1" + "jest-message-util": "^27.4.6", + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/globals": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.3.1.tgz", - "integrity": "sha512-Q651FWiWQAIFiN+zS51xqhdZ8g9b88nGCobC87argAxA7nMfNQq0Q0i9zTfQYgLa6qFXk2cGANEqfK051CZ8Pg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", + "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", "dev": true, "dependencies": { - "@jest/environment": "^27.3.1", - "@jest/types": "^27.2.5", - "expect": "^27.3.1" + "@jest/environment": "^27.4.6", + "@jest/types": "^27.4.2", + "expect": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/reporters": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.3.1.tgz", - "integrity": "sha512-m2YxPmL9Qn1emFVgZGEiMwDntDxRRQ2D58tiDQlwYTg5GvbFOKseYCcHtn0WsI8CG4vzPglo3nqbOiT8ySBT/w==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", + "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -929,14 +902,14 @@ "glob": "^7.1.2", "graceful-fs": "^4.2.4", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.4.6", + "jest-resolve": "^27.4.6", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -956,9 +929,9 @@ } }, "node_modules/@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", + "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", "dev": true, "dependencies": { "callsites": "^3.0.0", @@ -970,13 +943,13 @@ } }, "node_modules/@jest/test-result": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.3.1.tgz", - "integrity": "sha512-mLn6Thm+w2yl0opM8J/QnPTqrfS4FoXsXF2WIWJb2O/GBSyResL71BRuMYbYRsGt7ELwS5JGcEcGb52BNrumgg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", + "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", "dev": true, "dependencies": { - "@jest/console": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/types": "^27.4.2", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -985,38 +958,38 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz", - "integrity": "sha512-siySLo07IMEdSjA4fqEnxfIX8lB/lWYsBPwNFtkOvsFQvmBrL3yj3k3uFNZv/JDyApTakRpxbKLJ3CT8UGVCrA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", + "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", "dev": true, "dependencies": { - "@jest/test-result": "^27.3.1", + "@jest/test-result": "^27.4.6", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-runtime": "^27.3.1" + "jest-haste-map": "^27.4.6", + "jest-runtime": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/transform": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.3.1.tgz", - "integrity": "sha512-3fSvQ02kuvjOI1C1ssqMVBKJpZf6nwoCiSu00zAKh5nrp3SptNtZy/8s5deayHnqxhjD9CWDJ+yqQwuQ0ZafXQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", + "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", "dev": true, "dependencies": { "@babel/core": "^7.1.0", - "@jest/types": "^27.2.5", - "babel-plugin-istanbul": "^6.0.0", + "@jest/types": "^27.4.2", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.3.1", + "jest-haste-map": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-util": "^27.4.2", "micromatch": "^4.0.4", - "pirates": "^4.0.1", + "pirates": "^4.0.4", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" @@ -1026,9 +999,9 @@ } }, "node_modules/@jest/types": { - "version": "27.2.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", - "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -1086,9 +1059,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz", - "integrity": "sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", "dev": true, "dependencies": { "@sinonjs/commons": "^1.7.0" @@ -1104,9 +1077,9 @@ } }, "node_modules/@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "version": "7.1.18", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", + "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -1117,9 +1090,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -1155,9 +1128,9 @@ } }, "node_modules/@types/eslint": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz", - "integrity": "sha512-XhZKznR3i/W5dXqUhgU9fFdJekufbeBd5DALmkuXoeFcjbQcPk+2cL+WLHf6Q81HWAnM2vrslIHpGVyCAviRwg==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", + "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", "dev": true, "dependencies": { "@types/estree": "*", @@ -1165,9 +1138,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -1205,9 +1178,9 @@ } }, "node_modules/@types/har-format": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.7.tgz", - "integrity": "sha512-/TPzUG0tJn5x1TUcVLlDx2LqbE58hyOzDVAc9kf8SpOEmguHjU6bKUyfqb211AdqLOmU/SNyXvLKPNP5qTlfRw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.8.tgz", + "integrity": "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==", "dev": true }, "node_modules/@types/hoist-non-react-statics": { @@ -1220,15 +1193,15 @@ } }, "node_modules/@types/html-minifier-terser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.0.0.tgz", - "integrity": "sha512-NZwaaynfs1oIoLAV1vg18e7QMVDvw+6SQrdJc8w3BwUaoroVSf6EBj/Sk4PBWGxsq0dzhA2drbsuMC1/6C6KgQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "node_modules/@types/istanbul-lib-report": { @@ -1250,9 +1223,9 @@ } }, "node_modules/@types/jest": { - "version": "27.0.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", - "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", + "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", "dev": true, "dependencies": { "jest-diff": "^27.0.0", @@ -1281,15 +1254,15 @@ } }, "node_modules/@types/node": { - "version": "16.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", + "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", "dev": true }, "node_modules/@types/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", + "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", "dev": true }, "node_modules/@types/prop-types": { @@ -1304,9 +1277,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.30", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.30.tgz", - "integrity": "sha512-3Dt/A8gd3TCXi2aRe84y7cK1K8G+N9CZRDG8kDGguOKa0kf/ZkSwTmVIDPsm/KbQOVMaDJXwhBtuOXxqwdpWVg==", + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1314,18 +1287,18 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz", - "integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", "dev": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-redux": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.19.tgz", - "integrity": "sha512-L37dSCT0aoJnCgpR8Iuginlbxoh7qhWOXiaDqEsxVMrER1CmVhFD+63NxgJeT4pkmEM28oX0NH4S4f+sXHTZjA==", + "version": "7.1.22", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz", + "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -1360,13 +1333,14 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", - "integrity": "sha512-bekODL3Tqf36Yz8u+ilha4zGxL9mdB6LIsIoMAvvC5FAuWo4NpZYXtCbv7B2CeR1LhI/lLtLk+q4tbtxuoVuCg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.1.0", - "@typescript-eslint/scope-manager": "5.1.0", + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/type-utils": "5.10.0", + "@typescript-eslint/utils": "5.10.0", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", @@ -1391,18 +1365,16 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/experimental-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.1.0.tgz", - "integrity": "sha512-ovE9qUiZMOMgxQAESZsdBT+EXIfx/YUYAbwGUI6V03amFdOOxI9c6kitkgRvLkJaLusgMZ2xBhss+tQ0Y1HWxA==", + "node_modules/@typescript-eslint/parser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", + "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/typescript-estree": "5.10.0", + "debug": "^4.3.2" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1412,37 +1384,40 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", + "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/visitor-keys": "5.10.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.1.0.tgz", - "integrity": "sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", + "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "debug": "^4.3.2" + "@typescript-eslint/utils": "5.10.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1452,7 +1427,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "*" }, "peerDependenciesMeta": { "typescript": { @@ -1460,27 +1435,10 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz", - "integrity": "sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.1.0.tgz", - "integrity": "sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", + "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1491,13 +1449,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz", - "integrity": "sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", + "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/visitor-keys": "5.10.0", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -1517,14 +1475,18 @@ } } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz", - "integrity": "sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w==", + "node_modules/@typescript-eslint/utils": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", + "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.1.0", - "eslint-visitor-keys": "^3.0.0" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/typescript-estree": "5.10.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1532,15 +1494,26 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", - "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", + "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.10.0", + "eslint-visitor-keys": "^3.0.0" + }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@webassemblyjs/ast": { @@ -1940,16 +1913,16 @@ } }, "node_modules/babel-jest": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.3.1.tgz", - "integrity": "sha512-SjIF8hh/ir0peae2D6S6ZKRhUy7q/DnpH7k/V6fT4Bgs/LXXUztOpX4G2tCgq8mLo5HA9mN6NmlFMeYtKmIsTQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", + "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", "dev": true, "dependencies": { - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.2.0", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.4.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" @@ -1977,35 +1950,10 @@ "node": ">=8" } }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz", - "integrity": "sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/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/babel-plugin-jest-hoist": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", - "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", + "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -2041,12 +1989,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", - "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", + "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^27.2.0", + "babel-plugin-jest-hoist": "^27.4.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -2134,15 +2082,15 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz", - "integrity": "sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001265", - "electron-to-chromium": "^1.3.867", + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", - "node-releases": "^2.0.0", + "node-releases": "^2.0.1", "picocolors": "^1.0.0" }, "bin": { @@ -2247,9 +2195,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001269", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz", - "integrity": "sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w==", + "version": "1.0.30001300", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", + "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", "dev": true, "funding": { "type": "opencollective", @@ -2282,10 +2230,16 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2312,9 +2266,9 @@ } }, "node_modules/ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true }, "node_modules/cjs-module-lexer": { @@ -2324,9 +2278,9 @@ "dev": true }, "node_modules/clean-css": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.1.tgz", - "integrity": "sha512-ooQCa1/70oRfVdUUGjKpbHuxgMgm8BsDT5EBqBGvPxMoRoGXf4PNx5mMnkjzJ9Ptx4vvmDdha0QVh86QtYIk1g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", + "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", "dev": true, "dependencies": { "source-map": "~0.6.0" @@ -2413,9 +2367,9 @@ } }, "node_modules/commander": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz", - "integrity": "sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "engines": { "node": ">= 12" @@ -2504,16 +2458,16 @@ } }, "node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", + "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==", "dev": true, "dependencies": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^5.1.0", + "domhandler": "^4.3.0", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" @@ -2568,9 +2522,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "node_modules/data-urls": { "version": "2.0.0", @@ -2587,9 +2541,9 @@ } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2649,9 +2603,9 @@ } }, "node_modules/diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -2738,9 +2692,9 @@ } }, "node_modules/domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "dependencies": { "domelementtype": "^2.2.0" @@ -2777,9 +2731,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.871", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz", - "integrity": "sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q==", + "version": "1.4.48", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.48.tgz", + "integrity": "sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA==", "dev": true }, "node_modules/emittery": { @@ -2905,9 +2859,9 @@ } }, "node_modules/escodegen/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -3077,6 +3031,42 @@ } }, "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", + "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", @@ -3091,7 +3081,7 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", @@ -3100,7 +3090,7 @@ "node": ">=4" } }, - "node_modules/eslint-visitor-keys": { + "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", @@ -3167,9 +3157,9 @@ } }, "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -3188,9 +3178,9 @@ } }, "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -3256,34 +3246,20 @@ } }, "node_modules/expect": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.3.1.tgz", - "integrity": "sha512-MrNXV2sL9iDRebWPGOGFdPQRl2eDQNu/uhxIMShjjx74T6kC6jFIkmQ6OqXDtevjGUkyB2IT56RzDBqXf/QPCg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", + "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-regex-util": "^27.0.6" + "@jest/types": "^27.4.2", + "jest-get-type": "^27.4.0", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3297,9 +3273,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3309,7 +3285,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -3419,15 +3395,15 @@ } }, "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "funding": [ { "type": "individual", @@ -3579,9 +3555,9 @@ "dev": true }, "node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3594,16 +3570,16 @@ } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -3614,9 +3590,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, "node_modules/has": { @@ -3691,18 +3667,18 @@ "dev": true }, "node_modules/html-minifier-terser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.0.2.tgz", - "integrity": "sha512-AgYO3UGhMYQx2S/FBJT3EM0ZYcKmH6m9XL9c1v77BeK/tYJxGPxT1/AtsdUi4FcP8kZGmqqnItCcjFPcX9hk6A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "dependencies": { "camel-case": "^4.1.2", - "clean-css": "^5.1.5", - "commander": "^8.1.0", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^5.7.2" + "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" @@ -3711,16 +3687,70 @@ "node": ">=12" } }, + "node_modules/html-minifier-terser/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/html-minifier-terser/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/html-minifier-terser/node_modules/terser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "acorn": "^8.5.0" + }, + "peerDependenciesMeta": { + "acorn": { + "optional": true + } + } + }, + "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/html-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-cSUdckNOIqKc0nOrCJG7zkvzEIUcXjzEiVbKdEdIzW3BD5T4xPK6boV1mrTrPDZiL+aAr/j45eqbNL1akU2ZRA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", "lodash": "^4.17.21", - "pretty-error": "^3.0.4", + "pretty-error": "^4.0.0", "tapable": "^2.0.0" }, "engines": { @@ -3833,14 +3863,20 @@ ] }, "node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3858,9 +3894,9 @@ } }, "node_modules/import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -3871,6 +3907,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { @@ -3919,9 +3958,9 @@ } }, "node_modules/is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -4039,14 +4078,15 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", + "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", "dev": true, "dependencies": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" }, "engines": { @@ -4091,9 +4131,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", + "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4104,14 +4144,14 @@ } }, "node_modules/jest": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.3.1.tgz", - "integrity": "sha512-U2AX0AgQGd5EzMsiZpYt8HyZ+nSVIh5ujQ9CPp9EQZJMjXIiSZpJNweZl0swatKRoqHWgGKM3zaSwm4Zaz87ng==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", + "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", "dev": true, "dependencies": { - "@jest/core": "^27.3.1", + "@jest/core": "^27.4.7", "import-local": "^3.0.2", - "jest-cli": "^27.3.1" + "jest-cli": "^27.4.7" }, "bin": { "jest": "bin/jest.js" @@ -4129,12 +4169,12 @@ } }, "node_modules/jest-changed-files": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.3.0.tgz", - "integrity": "sha512-9DJs9garMHv4RhylUMZgbdCJ3+jHSkpL9aaVKp13xtXAD80qLTLrqcDZL1PHA9dYA0bCI86Nv2BhkLpLhrBcPg==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", + "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "execa": "^5.0.0", "throat": "^6.0.1" }, @@ -4143,27 +4183,27 @@ } }, "node_modules/jest-circus": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.3.1.tgz", - "integrity": "sha512-v1dsM9II6gvXokgqq6Yh2jHCpfg7ZqV4jWY66u7npz24JnhP3NHxI0sKT7+ZMQ7IrOWHYAaeEllOySbDbWsiXw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", + "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", "dev": true, "dependencies": { - "@jest/environment": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1", + "jest-each": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6", "slash": "^3.0.0", "stack-utils": "^2.0.3", "throat": "^6.0.1" @@ -4173,21 +4213,21 @@ } }, "node_modules/jest-cli": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.3.1.tgz", - "integrity": "sha512-WHnCqpfK+6EvT62me6WVs8NhtbjAS4/6vZJnk7/2+oOr50cwAzG4Wxt6RXX0hu6m1169ZGMlhYYUNeKBXCph/Q==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", + "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", "dev": true, "dependencies": { - "@jest/core": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/core": "^27.4.7", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", "import-local": "^3.0.2", - "jest-config": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-config": "^27.4.7", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "prompts": "^2.0.1", "yargs": "^16.2.0" }, @@ -4207,32 +4247,33 @@ } }, "node_modules/jest-config": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.3.1.tgz", - "integrity": "sha512-KY8xOIbIACZ/vdYCKSopL44I0xboxC751IX+DXL2+Wx6DKNycyEfV3rryC3BPm5Uq/BBqDoMrKuqLEUNJmMKKg==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", + "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", "dev": true, "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.3.1", - "@jest/types": "^27.2.5", - "babel-jest": "^27.3.1", + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.4.6", + "@jest/types": "^27.4.2", + "babel-jest": "^27.4.6", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-circus": "^27.3.1", - "jest-environment-jsdom": "^27.3.1", - "jest-environment-node": "^27.3.1", - "jest-get-type": "^27.3.1", - "jest-jasmine2": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-runner": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-circus": "^27.4.6", + "jest-environment-jsdom": "^27.4.6", + "jest-environment-node": "^27.4.6", + "jest-get-type": "^27.4.0", + "jest-jasmine2": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-runner": "^27.4.6", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "micromatch": "^4.0.4", - "pretty-format": "^27.3.1" + "pretty-format": "^27.4.6", + "slash": "^3.0.0" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -4247,24 +4288,24 @@ } }, "node_modules/jest-diff": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz", - "integrity": "sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", + "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", + "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -4274,33 +4315,33 @@ } }, "node_modules/jest-each": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.3.1.tgz", - "integrity": "sha512-E4SwfzKJWYcvOYCjOxhZcxwL+AY0uFMvdCOwvzgutJiaiodFjkxQQDxHm8FQBeTqDnSmKsQWn7ldMRzTn2zJaQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", + "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", - "jest-get-type": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-get-type": "^27.4.0", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz", - "integrity": "sha512-3MOy8qMzIkQlfb3W1TfrD7uZHj+xx8Olix5vMENkj5djPmRqndMaXtpnaZkxmxM+Qc3lo+yVzJjzuXbCcZjAlg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", + "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", "dev": true, "dependencies": { - "@jest/environment": "^27.3.1", - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1", + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2", "jsdom": "^16.6.0" }, "engines": { @@ -4308,47 +4349,47 @@ } }, "node_modules/jest-environment-node": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.3.1.tgz", - "integrity": "sha512-T89F/FgkE8waqrTSA7/ydMkcc52uYPgZZ6q8OaZgyiZkJb5QNNCF6oPZjH9IfPFfcc9uBWh1574N0kY0pSvTXw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", + "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", "dev": true, "dependencies": { - "@jest/environment": "^27.3.1", - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1" + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-get-type": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz", - "integrity": "sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-haste-map": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.3.1.tgz", - "integrity": "sha512-lYfNZIzwPccDJZIyk9Iz5iQMM/MH56NIIcGj7AFU1YyA4ewWFBl8z+YPJuSCRML/ee2cCt2y3W4K3VXPT6Nhzg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", + "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "jest-regex-util": "^27.4.0", + "jest-serializer": "^27.4.0", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "micromatch": "^4.0.4", "walker": "^1.0.7" }, @@ -4360,28 +4401,27 @@ } }, "node_modules/jest-jasmine2": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz", - "integrity": "sha512-WK11ZUetDQaC09w4/j7o4FZDUIp+4iYWH/Lik34Pv7ukL+DuXFGdnmmi7dT58J2ZYKFB5r13GyE0z3NPeyJmsg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", + "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", "dev": true, "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.3.1", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/source-map": "^27.4.0", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1", + "jest-each": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6", "throat": "^6.0.1" }, "engines": { @@ -4389,46 +4429,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz", - "integrity": "sha512-78QstU9tXbaHzwlRlKmTpjP9k4Pvre5l0r8Spo4SbFFVy/4Abg9I6ZjHwjg2QyKEAMg020XcjP+UgLZIY50yEg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", + "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", "dev": true, "dependencies": { - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz", - "integrity": "sha512-hX8N7zXS4k+8bC1Aj0OWpGb7D3gIXxYvPNK1inP5xvE4ztbz3rc4AkI6jGVaerepBnfWB17FL5lWFJT3s7qo8w==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", + "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^27.3.1", - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-message-util": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.3.1.tgz", - "integrity": "sha512-bh3JEmxsTZ/9rTm0jQrPElbY2+y48Rw2t47uMfByNyUVR+OfPh4anuyKsGqsNkXk/TI4JbLRZx+7p7Hdt6q1yg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", + "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.4", - "pretty-format": "^27.3.1", + "pretty-format": "^27.4.6", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -4437,24 +4477,24 @@ } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/jest-mock": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.3.0.tgz", - "integrity": "sha512-ziZiLk0elZOQjD08bLkegBzv5hCABu/c8Ytx45nJKkysQwGaonvmTxwjLqEA4qGdasq9o2I8/HtdGMNnVsMTGw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", + "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*" }, "engines": { @@ -4479,27 +4519,27 @@ } }, "node_modules/jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", + "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-resolve": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.3.1.tgz", - "integrity": "sha512-Dfzt25CFSPo3Y3GCbxynRBZzxq9AdyNN+x/v2IqYx6KVT5Z6me2Z/PsSGFSv3cOSUZqJ9pHxilao/I/m9FouLw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", + "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", + "jest-haste-map": "^27.4.6", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" @@ -4509,45 +4549,45 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz", - "integrity": "sha512-X7iLzY8pCiYOnvYo2YrK3P9oSE8/3N2f4pUZMJ8IUcZnT81vlSonya1KTO9ZfKGuC+svE6FHK/XOb8SsoRUV1A==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", + "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.3.1" + "@jest/types": "^27.4.2", + "jest-regex-util": "^27.4.0", + "jest-snapshot": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-runner": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.3.1.tgz", - "integrity": "sha512-r4W6kBn6sPr3TBwQNmqE94mPlYVn7fLBseeJfo4E2uCTmAyDFm2O5DYAQAFP7Q3YfiA/bMwg8TVsciP7k0xOww==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", + "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", "dev": true, "dependencies": { - "@jest/console": "^27.3.1", - "@jest/environment": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/environment": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.3.1", - "jest-environment-node": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-leak-detector": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "jest-docblock": "^27.4.0", + "jest-environment-jsdom": "^27.4.6", + "jest-environment-node": "^27.4.6", + "jest-haste-map": "^27.4.6", + "jest-leak-detector": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-resolve": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "source-map-support": "^0.5.6", "throat": "^6.0.1" }, @@ -4556,46 +4596,42 @@ } }, "node_modules/jest-runtime": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.3.1.tgz", - "integrity": "sha512-qtO6VxPbS8umqhEDpjA4pqTkKQ1Hy4ZSi9mDVeE9Za7LKBo2LdW2jmT+Iod3XFaJqINikZQsn2wEi0j9wPRbLg==", - "dev": true, - "dependencies": { - "@jest/console": "^27.3.1", - "@jest/environment": "^27.3.1", - "@jest/globals": "^27.3.1", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", - "@types/yargs": "^16.0.0", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", + "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/globals": "^27.4.6", + "@jest/source-map": "^27.4.0", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "execa": "^5.0.0", - "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-mock": "^27.3.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-haste-map": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-mock": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.2.0" + "strip-bom": "^4.0.0" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", + "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", "dev": true, "dependencies": { "@types/node": "*", @@ -4606,34 +4642,32 @@ } }, "node_modules/jest-snapshot": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.3.1.tgz", - "integrity": "sha512-APZyBvSgQgOT0XumwfFu7X3G5elj6TGhCBLbBdn3R1IzYustPGPE38F51dBWMQ8hRXa9je0vAdeVDtqHLvB6lg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", + "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", "dev": true, "dependencies": { "@babel/core": "^7.7.2", "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/babel__traverse": "^7.0.4", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "graceful-fs": "^4.2.4", - "jest-diff": "^27.3.1", - "jest-get-type": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-util": "^27.3.1", + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "jest-haste-map": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-util": "^27.4.2", "natural-compare": "^1.4.0", - "pretty-format": "^27.3.1", + "pretty-format": "^27.4.6", "semver": "^7.3.2" }, "engines": { @@ -4641,12 +4675,12 @@ } }, "node_modules/jest-util": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.3.1.tgz", - "integrity": "sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", + "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4658,26 +4692,26 @@ } }, "node_modules/jest-validate": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.3.1.tgz", - "integrity": "sha512-3H0XCHDFLA9uDII67Bwi1Vy7AqwA5HqEEjyy934lgVhtJ3eisw6ShOF1MDmRPspyikef5MyExvIm0/TuLzZ86Q==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", + "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.3.1", + "jest-get-type": "^27.4.0", "leven": "^3.1.0", - "pretty-format": "^27.3.1" + "pretty-format": "^27.4.6" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { "node": ">=10" @@ -4687,17 +4721,17 @@ } }, "node_modules/jest-watcher": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.3.1.tgz", - "integrity": "sha512-9/xbV6chABsGHWh9yPaAGYVVKurWoP3ZMCv6h+O1v9/+pkOroigs6WzZ0e9gLP/njokUwM7yQhr01LKJVMkaZA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", + "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", "dev": true, "dependencies": { - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.3.1", + "jest-util": "^27.4.2", "string-length": "^4.0.1" }, "engines": { @@ -4705,9 +4739,9 @@ } }, "node_modules/jest-worker": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", - "integrity": "sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", + "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", "dev": true, "dependencies": { "@types/node": "*", @@ -4798,9 +4832,9 @@ } }, "node_modules/jsdom/node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4887,9 +4921,9 @@ } }, "node_modules/klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", "dev": true, "engines": { "node": ">= 8" @@ -4927,9 +4961,9 @@ } }, "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "dev": true, "dependencies": { "big.js": "^5.2.2", @@ -4958,12 +4992,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -5045,12 +5073,12 @@ "dev": true }, "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "dependencies": { - "tmpl": "1.0.x" + "tmpl": "1.0.5" } }, "node_modules/merge-stream": { @@ -5082,21 +5110,21 @@ } }, "node_modules/mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "dev": true, "dependencies": { - "mime-db": "1.50.0" + "mime-db": "1.51.0" }, "engines": { "node": ">= 0.6" @@ -5157,9 +5185,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.1.30", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -5211,19 +5239,10 @@ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", "dev": true }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/node-releases": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz", - "integrity": "sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, "node_modules/normalize-path": { @@ -5274,9 +5293,9 @@ } }, "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5460,9 +5479,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -5472,13 +5491,10 @@ } }, "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, "engines": { "node": ">= 6" } @@ -5496,14 +5512,14 @@ } }, "node_modules/postcss": { - "version": "8.3.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz", - "integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==", + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", + "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "dev": true, "dependencies": { - "nanoid": "^3.1.28", - "picocolors": "^0.2.1", - "source-map-js": "^0.6.2" + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5573,9 +5589,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -5586,15 +5602,9 @@ } }, "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "node_modules/postcss/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "node_modules/prelude-ls": { @@ -5607,9 +5617,9 @@ } }, "node_modules/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -5631,22 +5641,21 @@ } }, "node_modules/pretty-error": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-3.0.4.tgz", - "integrity": "sha512-ytLFLfv1So4AO1UkoBF6GXQgJRaKbiSiGFICaOPNwQ3CMvBvXpLRubeQWyPGnsbV/t9ml9qto6IeCsho0aEvwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "dependencies": { "lodash": "^4.17.20", - "renderkid": "^2.0.6" + "renderkid": "^3.0.0" } }, "node_modules/pretty-format": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", - "integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", + "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" @@ -5690,13 +5699,13 @@ } }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/prop-types/node_modules/react-is": { @@ -5720,9 +5729,9 @@ } }, "node_modules/qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "dependencies": { "side-channel": "^1.0.4" }, @@ -5790,20 +5799,19 @@ "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/react-redux": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz", - "integrity": "sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz", + "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==", "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", "hoist-non-react-statics": "^3.3.2", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", - "react-is": "^16.13.1" + "react-is": "^17.0.2" }, "peerDependencies": { "react": "^16.8.3 || ^17" @@ -5817,11 +5825,6 @@ } } }, - "node_modules/react-redux/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -5860,9 +5863,9 @@ } }, "node_modules/redux": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz", - "integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", + "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", "dependencies": { "@babel/runtime": "^7.9.2" } @@ -5894,37 +5897,16 @@ } }, "node_modules/renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, "dependencies": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - } - }, - "node_modules/renderkid/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/renderkid/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "strip-ansi": "^6.0.1" } }, "node_modules/require-directory": { @@ -5946,13 +5928,17 @@ } }, "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", "dev": true, "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6076,12 +6062,14 @@ "dev": true }, "node_modules/sass": { - "version": "1.43.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.43.2.tgz", - "integrity": "sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz", + "integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==", "dev": true, "dependencies": { - "chokidar": ">=3.0.0 <4.0.0" + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" @@ -6234,9 +6222,9 @@ } }, "node_modules/signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, "node_modules/sisteransi": { @@ -6295,18 +6283,18 @@ } }, "node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -6340,15 +6328,6 @@ "node": ">=8" } }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6470,6 +6449,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -6477,13 +6468,12 @@ "dev": true }, "node_modules/table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "dependencies": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", @@ -6494,9 +6484,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -6540,31 +6530,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", + "integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==", "dev": true, "dependencies": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", + "jest-worker": "^27.4.1", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", @@ -6592,6 +6564,26 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -6601,13 +6593,32 @@ "randombytes": "^2.1.0" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "node_modules/terser-webpack-plugin/node_modules/terser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "acorn": "^8.5.0" + }, + "peerDependenciesMeta": { + "acorn": { + "optional": true + } + } }, - "node_modules/terser/node_modules/source-map": { + "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", @@ -6696,9 +6707,9 @@ } }, "node_modules/ts-jest": { - "version": "27.0.7", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.7.tgz", - "integrity": "sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q==", + "version": "27.1.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", + "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -6720,6 +6731,7 @@ "@babel/core": ">=7.0.0-beta.0 <8", "@types/jest": "^27.0.0", "babel-jest": ">=27.0.0 <28", + "esbuild": "~0.14.0", "jest": "^27.0.0", "typescript": ">=3.8 <5.0" }, @@ -6732,6 +6744,9 @@ }, "babel-jest": { "optional": true + }, + "esbuild": { + "optional": true } } }, @@ -6755,9 +6770,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -6767,9 +6782,9 @@ } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.1.tgz", - "integrity": "sha512-n5CMlUUj+N5pjBhBACLq4jdr9cPTitySCjIosoQm0zwK99gmrcTGAfY9CwxRFT9+9OleNWXPRUcxsKP4AYExxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -6868,9 +6883,9 @@ } }, "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6916,9 +6931,9 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", - "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -6960,18 +6975,18 @@ } }, "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "dependencies": { - "makeerror": "1.0.x" + "makeerror": "1.0.12" } }, "node_modules/watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", + "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -6991,9 +7006,9 @@ } }, "node_modules/webpack": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.59.0.tgz", - "integrity": "sha512-2HiFHKnWIb/cBfOfgssQn8XIRvntISXiz//F1q1+hKMs+uzC1zlVCJZEP7XqI1wzrDyc/ZdB4G+MYtz5biJxCA==", + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", + "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.0", @@ -7010,7 +7025,7 @@ "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "json-parse-better-errors": "^1.0.2", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -7018,8 +7033,8 @@ "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.2" }, "bin": { "webpack": "bin/webpack.js" @@ -7113,9 +7128,9 @@ } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -7134,9 +7149,9 @@ } }, "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, "engines": { "node": ">=10.13.0" @@ -7237,9 +7252,9 @@ } }, "node_modules/ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", "dev": true, "engines": { "node": ">=8.3.0" @@ -7335,26 +7350,26 @@ } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", + "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", "dev": true }, "@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", + "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.7", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -7364,12 +7379,12 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" } }, "semver": { @@ -7387,12 +7402,12 @@ } }, "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", + "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", "dev": true, "requires": { - "@babel/types": "^7.15.6", + "@babel/types": "^7.16.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -7406,14 +7421,14 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", "semver": "^6.3.0" }, "dependencies": { @@ -7425,144 +7440,123 @@ } } }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" } }, - "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, - "@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "@babel/helper-module-transforms": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", + "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, + "@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "dev": true + }, "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", + "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -7626,9 +7620,9 @@ } }, "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", + "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -7740,68 +7734,69 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", + "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" }, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" } } } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", + "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.16.8", + "@babel/types": "^7.16.8", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" } }, "globals": { @@ -7813,12 +7808,12 @@ } }, "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, @@ -7829,9 +7824,9 @@ "dev": true }, "@discoveryjs/json-ext": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", - "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", + "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, "@eslint/eslintrc": { @@ -7871,9 +7866,9 @@ } }, "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -7904,49 +7899,49 @@ "dev": true }, "@jest/console": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.3.1.tgz", - "integrity": "sha512-RkFNWmv0iui+qsOr/29q9dyfKTTT5DCuP31kUwg7rmOKPT/ozLeGLKJKVIiOfbiKyleUZKIrHwhmiZWVe8IMdw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", + "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.3.1", - "jest-util": "^27.3.1", + "jest-message-util": "^27.4.6", + "jest-util": "^27.4.2", "slash": "^3.0.0" } }, "@jest/core": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.3.1.tgz", - "integrity": "sha512-DMNE90RR5QKx0EA+wqe3/TNEwiRpOkhshKNxtLxd4rt3IZpCt+RSL+FoJsGeblRZmqdK4upHA/mKKGPPRAifhg==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", + "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", "dev": true, "requires": { - "@jest/console": "^27.3.1", - "@jest/reporters": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/reporters": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.3.0", - "jest-config": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-resolve-dependencies": "^27.3.1", - "jest-runner": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", - "jest-watcher": "^27.3.1", + "jest-changed-files": "^27.4.2", + "jest-config": "^27.4.7", + "jest-haste-map": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-resolve-dependencies": "^27.4.6", + "jest-runner": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", + "jest-watcher": "^27.4.6", "micromatch": "^4.0.4", "rimraf": "^3.0.0", "slash": "^3.0.0", @@ -7954,53 +7949,53 @@ } }, "@jest/environment": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.3.1.tgz", - "integrity": "sha512-BCKCj4mOVLme6Tanoyc9k0ultp3pnmuyHw73UHRPeeZxirsU/7E3HC4le/VDb/SMzE1JcPnto+XBKFOcoiJzVw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", + "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", "dev": true, "requires": { - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0" + "jest-mock": "^27.4.6" } }, "@jest/fake-timers": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.3.1.tgz", - "integrity": "sha512-M3ZFgwwlqJtWZ+QkBG5NmC23A9w+A6ZxNsO5nJxJsKYt4yguBd3i8TpjQz5NfCX91nEve1KqD9RA2Q+Q1uWqoA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", + "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^27.3.1", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1" + "jest-message-util": "^27.4.6", + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2" } }, "@jest/globals": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.3.1.tgz", - "integrity": "sha512-Q651FWiWQAIFiN+zS51xqhdZ8g9b88nGCobC87argAxA7nMfNQq0Q0i9zTfQYgLa6qFXk2cGANEqfK051CZ8Pg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", + "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", "dev": true, "requires": { - "@jest/environment": "^27.3.1", - "@jest/types": "^27.2.5", - "expect": "^27.3.1" + "@jest/environment": "^27.4.6", + "@jest/types": "^27.4.2", + "expect": "^27.4.6" } }, "@jest/reporters": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.3.1.tgz", - "integrity": "sha512-m2YxPmL9Qn1emFVgZGEiMwDntDxRRQ2D58tiDQlwYTg5GvbFOKseYCcHtn0WsI8CG4vzPglo3nqbOiT8ySBT/w==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", + "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -8008,14 +8003,14 @@ "glob": "^7.1.2", "graceful-fs": "^4.2.4", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.4.6", + "jest-resolve": "^27.4.6", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -8024,9 +8019,9 @@ } }, "@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", + "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", "dev": true, "requires": { "callsites": "^3.0.0", @@ -8035,56 +8030,56 @@ } }, "@jest/test-result": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.3.1.tgz", - "integrity": "sha512-mLn6Thm+w2yl0opM8J/QnPTqrfS4FoXsXF2WIWJb2O/GBSyResL71BRuMYbYRsGt7ELwS5JGcEcGb52BNrumgg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", + "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", "dev": true, "requires": { - "@jest/console": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/types": "^27.4.2", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz", - "integrity": "sha512-siySLo07IMEdSjA4fqEnxfIX8lB/lWYsBPwNFtkOvsFQvmBrL3yj3k3uFNZv/JDyApTakRpxbKLJ3CT8UGVCrA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", + "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", "dev": true, "requires": { - "@jest/test-result": "^27.3.1", + "@jest/test-result": "^27.4.6", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-runtime": "^27.3.1" + "jest-haste-map": "^27.4.6", + "jest-runtime": "^27.4.6" } }, "@jest/transform": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.3.1.tgz", - "integrity": "sha512-3fSvQ02kuvjOI1C1ssqMVBKJpZf6nwoCiSu00zAKh5nrp3SptNtZy/8s5deayHnqxhjD9CWDJ+yqQwuQ0ZafXQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", + "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^27.2.5", - "babel-plugin-istanbul": "^6.0.0", + "@jest/types": "^27.4.2", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.3.1", + "jest-haste-map": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-util": "^27.4.2", "micromatch": "^4.0.4", - "pirates": "^4.0.1", + "pirates": "^4.0.4", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" } }, "@jest/types": { - "version": "27.2.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", - "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -8130,9 +8125,9 @@ } }, "@sinonjs/fake-timers": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz", - "integrity": "sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -8145,9 +8140,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "version": "7.1.18", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", + "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -8158,9 +8153,9 @@ } }, "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -8196,9 +8191,9 @@ } }, "@types/eslint": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz", - "integrity": "sha512-XhZKznR3i/W5dXqUhgU9fFdJekufbeBd5DALmkuXoeFcjbQcPk+2cL+WLHf6Q81HWAnM2vrslIHpGVyCAviRwg==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", + "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", "dev": true, "requires": { "@types/estree": "*", @@ -8206,9 +8201,9 @@ } }, "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", "dev": true, "requires": { "@types/eslint": "*", @@ -8246,9 +8241,9 @@ } }, "@types/har-format": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.7.tgz", - "integrity": "sha512-/TPzUG0tJn5x1TUcVLlDx2LqbE58hyOzDVAc9kf8SpOEmguHjU6bKUyfqb211AdqLOmU/SNyXvLKPNP5qTlfRw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.8.tgz", + "integrity": "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==", "dev": true }, "@types/hoist-non-react-statics": { @@ -8261,15 +8256,15 @@ } }, "@types/html-minifier-terser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.0.0.tgz", - "integrity": "sha512-NZwaaynfs1oIoLAV1vg18e7QMVDvw+6SQrdJc8w3BwUaoroVSf6EBj/Sk4PBWGxsq0dzhA2drbsuMC1/6C6KgQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "@types/istanbul-lib-report": { @@ -8291,9 +8286,9 @@ } }, "@types/jest": { - "version": "27.0.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", - "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", + "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", "dev": true, "requires": { "jest-diff": "^27.0.0", @@ -8322,15 +8317,15 @@ } }, "@types/node": { - "version": "16.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", + "version": "17.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", + "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", "dev": true }, "@types/prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", + "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", "dev": true }, "@types/prop-types": { @@ -8345,9 +8340,9 @@ "dev": true }, "@types/react": { - "version": "17.0.30", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.30.tgz", - "integrity": "sha512-3Dt/A8gd3TCXi2aRe84y7cK1K8G+N9CZRDG8kDGguOKa0kf/ZkSwTmVIDPsm/KbQOVMaDJXwhBtuOXxqwdpWVg==", + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -8355,18 +8350,18 @@ } }, "@types/react-dom": { - "version": "17.0.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz", - "integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==", + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", + "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", "dev": true, "requires": { "@types/react": "*" } }, "@types/react-redux": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.19.tgz", - "integrity": "sha512-L37dSCT0aoJnCgpR8Iuginlbxoh7qhWOXiaDqEsxVMrER1CmVhFD+63NxgJeT4pkmEM28oX0NH4S4f+sXHTZjA==", + "version": "7.1.22", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz", + "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==", "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -8401,84 +8396,69 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", - "integrity": "sha512-bekODL3Tqf36Yz8u+ilha4zGxL9mdB6LIsIoMAvvC5FAuWo4NpZYXtCbv7B2CeR1LhI/lLtLk+q4tbtxuoVuCg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.1.0", - "@typescript-eslint/scope-manager": "5.1.0", + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/type-utils": "5.10.0", + "@typescript-eslint/utils": "5.10.0", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", "regexpp": "^3.2.0", "semver": "^7.3.5", "tsutils": "^3.21.0" - }, - "dependencies": { - "@typescript-eslint/experimental-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.1.0.tgz", - "integrity": "sha512-ovE9qUiZMOMgxQAESZsdBT+EXIfx/YUYAbwGUI6V03amFdOOxI9c6kitkgRvLkJaLusgMZ2xBhss+tQ0Y1HWxA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - } - } - } - } } }, "@typescript-eslint/parser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.1.0.tgz", - "integrity": "sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", + "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.1.0", - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/typescript-estree": "5.1.0", + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/typescript-estree": "5.10.0", "debug": "^4.3.2" } }, "@typescript-eslint/scope-manager": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz", - "integrity": "sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", + "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/visitor-keys": "5.10.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", + "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0" + "@typescript-eslint/utils": "5.10.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.1.0.tgz", - "integrity": "sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", + "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz", - "integrity": "sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", + "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", - "@typescript-eslint/visitor-keys": "5.1.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/visitor-keys": "5.10.0", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -8486,22 +8466,28 @@ "tsutils": "^3.21.0" } }, + "@typescript-eslint/utils": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", + "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.10.0", + "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/typescript-estree": "5.10.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, "@typescript-eslint/visitor-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz", - "integrity": "sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", + "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.1.0", + "@typescript-eslint/types": "5.10.0", "eslint-visitor-keys": "^3.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz", - "integrity": "sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==", - "dev": true - } } }, "@webassemblyjs/ast": { @@ -8837,16 +8823,16 @@ } }, "babel-jest": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.3.1.tgz", - "integrity": "sha512-SjIF8hh/ir0peae2D6S6ZKRhUy7q/DnpH7k/V6fT4Bgs/LXXUztOpX4G2tCgq8mLo5HA9mN6NmlFMeYtKmIsTQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", + "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", "dev": true, "requires": { - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.2.0", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.4.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" @@ -8863,33 +8849,12 @@ "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz", - "integrity": "sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "babel-plugin-jest-hoist": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", - "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", + "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -8919,12 +8884,12 @@ } }, "babel-preset-jest": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", - "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", + "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^27.2.0", + "babel-plugin-jest-hoist": "^27.4.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -8983,15 +8948,15 @@ "dev": true }, "browserslist": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz", - "integrity": "sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", + "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001265", - "electron-to-chromium": "^1.3.867", + "caniuse-lite": "^1.0.30001286", + "electron-to-chromium": "^1.4.17", "escalade": "^3.1.1", - "node-releases": "^2.0.0", + "node-releases": "^2.0.1", "picocolors": "^1.0.0" } }, @@ -9060,9 +9025,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001269", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz", - "integrity": "sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w==", + "version": "1.0.30001300", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", + "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", "dev": true }, "chalk": { @@ -9082,9 +9047,9 @@ "dev": true }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -9104,9 +9069,9 @@ "dev": true }, "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", "dev": true }, "cjs-module-lexer": { @@ -9116,9 +9081,9 @@ "dev": true }, "clean-css": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.1.tgz", - "integrity": "sha512-ooQCa1/70oRfVdUUGjKpbHuxgMgm8BsDT5EBqBGvPxMoRoGXf4PNx5mMnkjzJ9Ptx4vvmDdha0QVh86QtYIk1g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", + "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -9189,9 +9154,9 @@ } }, "commander": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz", - "integrity": "sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true }, "concat-map": { @@ -9254,16 +9219,16 @@ } }, "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", + "integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==", "dev": true, "requires": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "css-what": "^5.1.0", + "domhandler": "^4.3.0", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" } }, "css-what": { @@ -9302,9 +9267,9 @@ } }, "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" }, "data-urls": { "version": "2.0.0", @@ -9318,9 +9283,9 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -9363,9 +9328,9 @@ "dev": true }, "diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", "dev": true }, "dir-glob": { @@ -9430,9 +9395,9 @@ } }, "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz", + "integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==", "dev": true, "requires": { "domelementtype": "^2.2.0" @@ -9460,9 +9425,9 @@ } }, "electron-to-chromium": { - "version": "1.3.871", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz", - "integrity": "sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q==", + "version": "1.4.48", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.48.tgz", + "integrity": "sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA==", "dev": true }, "emittery": { @@ -9546,9 +9511,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "levn": { @@ -9640,6 +9605,29 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -9684,26 +9672,26 @@ } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true } } }, "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", + "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", "dev": true }, "espree": { @@ -9741,9 +9729,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -9758,9 +9746,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -9807,25 +9795,15 @@ "dev": true }, "expect": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.3.1.tgz", - "integrity": "sha512-MrNXV2sL9iDRebWPGOGFdPQRl2eDQNu/uhxIMShjjx74T6kC6jFIkmQ6OqXDtevjGUkyB2IT56RzDBqXf/QPCg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", + "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", "dev": true, "requires": { - "@jest/types": "^27.2.5", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-regex-util": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } + "@jest/types": "^27.4.2", + "jest-get-type": "^27.4.0", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6" } }, "fast-deep-equal": { @@ -9841,9 +9819,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -9938,15 +9916,15 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "form-data": { "version": "3.0.1", @@ -10047,32 +10025,32 @@ "dev": true }, "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", + "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "dev": true }, "has": { @@ -10131,30 +10109,65 @@ "dev": true }, "html-minifier-terser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.0.2.tgz", - "integrity": "sha512-AgYO3UGhMYQx2S/FBJT3EM0ZYcKmH6m9XL9c1v77BeK/tYJxGPxT1/AtsdUi4FcP8kZGmqqnItCcjFPcX9hk6A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "requires": { "camel-case": "^4.1.2", - "clean-css": "^5.1.5", - "commander": "^8.1.0", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^5.7.2" + "terser": "^5.10.0" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "optional": true, + "peer": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "terser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + } } }, "html-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-cSUdckNOIqKc0nOrCJG7zkvzEIUcXjzEiVbKdEdIzW3BD5T4xPK6boV1mrTrPDZiL+aAr/j45eqbNL1akU2ZRA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, "requires": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", "lodash": "^4.17.21", - "pretty-error": "^3.0.4", + "pretty-error": "^4.0.0", "tapable": "^2.0.0" } }, @@ -10219,9 +10232,15 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", "dev": true }, "import-fresh": { @@ -10235,9 +10254,9 @@ } }, "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "requires": { "pkg-dir": "^4.2.0", @@ -10281,9 +10300,9 @@ } }, "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "requires": { "has": "^1.0.3" @@ -10368,14 +10387,15 @@ "dev": true }, "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", + "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", "dev": true, "requires": { - "@babel/core": "^7.7.5", + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" }, "dependencies": { @@ -10410,9 +10430,9 @@ } }, "istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", + "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -10420,276 +10440,276 @@ } }, "jest": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.3.1.tgz", - "integrity": "sha512-U2AX0AgQGd5EzMsiZpYt8HyZ+nSVIh5ujQ9CPp9EQZJMjXIiSZpJNweZl0swatKRoqHWgGKM3zaSwm4Zaz87ng==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", + "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", "dev": true, "requires": { - "@jest/core": "^27.3.1", + "@jest/core": "^27.4.7", "import-local": "^3.0.2", - "jest-cli": "^27.3.1" + "jest-cli": "^27.4.7" } }, "jest-changed-files": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.3.0.tgz", - "integrity": "sha512-9DJs9garMHv4RhylUMZgbdCJ3+jHSkpL9aaVKp13xtXAD80qLTLrqcDZL1PHA9dYA0bCI86Nv2BhkLpLhrBcPg==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", + "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "execa": "^5.0.0", "throat": "^6.0.1" } }, "jest-circus": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.3.1.tgz", - "integrity": "sha512-v1dsM9II6gvXokgqq6Yh2jHCpfg7ZqV4jWY66u7npz24JnhP3NHxI0sKT7+ZMQ7IrOWHYAaeEllOySbDbWsiXw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", + "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", "dev": true, "requires": { - "@jest/environment": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1", + "jest-each": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6", "slash": "^3.0.0", "stack-utils": "^2.0.3", "throat": "^6.0.1" } }, "jest-cli": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.3.1.tgz", - "integrity": "sha512-WHnCqpfK+6EvT62me6WVs8NhtbjAS4/6vZJnk7/2+oOr50cwAzG4Wxt6RXX0hu6m1169ZGMlhYYUNeKBXCph/Q==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", + "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", "dev": true, "requires": { - "@jest/core": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/core": "^27.4.7", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", "import-local": "^3.0.2", - "jest-config": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-config": "^27.4.7", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "prompts": "^2.0.1", "yargs": "^16.2.0" } }, "jest-config": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.3.1.tgz", - "integrity": "sha512-KY8xOIbIACZ/vdYCKSopL44I0xboxC751IX+DXL2+Wx6DKNycyEfV3rryC3BPm5Uq/BBqDoMrKuqLEUNJmMKKg==", + "version": "27.4.7", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", + "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.3.1", - "@jest/types": "^27.2.5", - "babel-jest": "^27.3.1", + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.4.6", + "@jest/types": "^27.4.2", + "babel-jest": "^27.4.6", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-circus": "^27.3.1", - "jest-environment-jsdom": "^27.3.1", - "jest-environment-node": "^27.3.1", - "jest-get-type": "^27.3.1", - "jest-jasmine2": "^27.3.1", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-runner": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-circus": "^27.4.6", + "jest-environment-jsdom": "^27.4.6", + "jest-environment-node": "^27.4.6", + "jest-get-type": "^27.4.0", + "jest-jasmine2": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-runner": "^27.4.6", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "micromatch": "^4.0.4", - "pretty-format": "^27.3.1" + "pretty-format": "^27.4.6", + "slash": "^3.0.0" } }, "jest-diff": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz", - "integrity": "sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", + "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" } }, "jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", + "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.3.1.tgz", - "integrity": "sha512-E4SwfzKJWYcvOYCjOxhZcxwL+AY0uFMvdCOwvzgutJiaiodFjkxQQDxHm8FQBeTqDnSmKsQWn7ldMRzTn2zJaQ==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", + "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", - "jest-get-type": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-get-type": "^27.4.0", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6" } }, "jest-environment-jsdom": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz", - "integrity": "sha512-3MOy8qMzIkQlfb3W1TfrD7uZHj+xx8Olix5vMENkj5djPmRqndMaXtpnaZkxmxM+Qc3lo+yVzJjzuXbCcZjAlg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", + "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", "dev": true, "requires": { - "@jest/environment": "^27.3.1", - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1", + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2", "jsdom": "^16.6.0" } }, "jest-environment-node": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.3.1.tgz", - "integrity": "sha512-T89F/FgkE8waqrTSA7/ydMkcc52uYPgZZ6q8OaZgyiZkJb5QNNCF6oPZjH9IfPFfcc9uBWh1574N0kY0pSvTXw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", + "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", "dev": true, "requires": { - "@jest/environment": "^27.3.1", - "@jest/fake-timers": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", - "jest-mock": "^27.3.0", - "jest-util": "^27.3.1" + "jest-mock": "^27.4.6", + "jest-util": "^27.4.2" } }, "jest-get-type": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz", - "integrity": "sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", "dev": true }, "jest-haste-map": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.3.1.tgz", - "integrity": "sha512-lYfNZIzwPccDJZIyk9Iz5iQMM/MH56NIIcGj7AFU1YyA4ewWFBl8z+YPJuSCRML/ee2cCt2y3W4K3VXPT6Nhzg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", + "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "jest-regex-util": "^27.4.0", + "jest-serializer": "^27.4.0", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "micromatch": "^4.0.4", "walker": "^1.0.7" } }, "jest-jasmine2": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz", - "integrity": "sha512-WK11ZUetDQaC09w4/j7o4FZDUIp+4iYWH/Lik34Pv7ukL+DuXFGdnmmi7dT58J2ZYKFB5r13GyE0z3NPeyJmsg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", + "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.3.1", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/environment": "^27.4.6", + "@jest/source-map": "^27.4.0", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "is-generator-fn": "^2.0.0", - "jest-each": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "pretty-format": "^27.3.1", + "jest-each": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", + "pretty-format": "^27.4.6", "throat": "^6.0.1" } }, "jest-leak-detector": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz", - "integrity": "sha512-78QstU9tXbaHzwlRlKmTpjP9k4Pvre5l0r8Spo4SbFFVy/4Abg9I6ZjHwjg2QyKEAMg020XcjP+UgLZIY50yEg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", + "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", "dev": true, "requires": { - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" } }, "jest-matcher-utils": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz", - "integrity": "sha512-hX8N7zXS4k+8bC1Aj0OWpGb7D3gIXxYvPNK1inP5xvE4ztbz3rc4AkI6jGVaerepBnfWB17FL5lWFJT3s7qo8w==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", + "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.3.1", - "jest-get-type": "^27.3.1", - "pretty-format": "^27.3.1" + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.6" } }, "jest-message-util": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.3.1.tgz", - "integrity": "sha512-bh3JEmxsTZ/9rTm0jQrPElbY2+y48Rw2t47uMfByNyUVR+OfPh4anuyKsGqsNkXk/TI4JbLRZx+7p7Hdt6q1yg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", + "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.4", - "pretty-format": "^27.3.1", + "pretty-format": "^27.4.6", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.7" } } } }, "jest-mock": { - "version": "27.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.3.0.tgz", - "integrity": "sha512-ziZiLk0elZOQjD08bLkegBzv5hCABu/c8Ytx45nJKkysQwGaonvmTxwjLqEA4qGdasq9o2I8/HtdGMNnVsMTGw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", + "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*" } }, @@ -10701,108 +10721,104 @@ "requires": {} }, "jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", + "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", "dev": true }, "jest-resolve": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.3.1.tgz", - "integrity": "sha512-Dfzt25CFSPo3Y3GCbxynRBZzxq9AdyNN+x/v2IqYx6KVT5Z6me2Z/PsSGFSv3cOSUZqJ9pHxilao/I/m9FouLw==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", + "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", + "jest-haste-map": "^27.4.6", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-util": "^27.4.2", + "jest-validate": "^27.4.6", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" } }, "jest-resolve-dependencies": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz", - "integrity": "sha512-X7iLzY8pCiYOnvYo2YrK3P9oSE8/3N2f4pUZMJ8IUcZnT81vlSonya1KTO9ZfKGuC+svE6FHK/XOb8SsoRUV1A==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", + "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", "dev": true, "requires": { - "@jest/types": "^27.2.5", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.3.1" + "@jest/types": "^27.4.2", + "jest-regex-util": "^27.4.0", + "jest-snapshot": "^27.4.6" } }, "jest-runner": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.3.1.tgz", - "integrity": "sha512-r4W6kBn6sPr3TBwQNmqE94mPlYVn7fLBseeJfo4E2uCTmAyDFm2O5DYAQAFP7Q3YfiA/bMwg8TVsciP7k0xOww==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", + "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", "dev": true, "requires": { - "@jest/console": "^27.3.1", - "@jest/environment": "^27.3.1", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/console": "^27.4.6", + "@jest/environment": "^27.4.6", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.3.1", - "jest-environment-node": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-leak-detector": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-runtime": "^27.3.1", - "jest-util": "^27.3.1", - "jest-worker": "^27.3.1", + "jest-docblock": "^27.4.0", + "jest-environment-jsdom": "^27.4.6", + "jest-environment-node": "^27.4.6", + "jest-haste-map": "^27.4.6", + "jest-leak-detector": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-resolve": "^27.4.6", + "jest-runtime": "^27.4.6", + "jest-util": "^27.4.2", + "jest-worker": "^27.4.6", "source-map-support": "^0.5.6", "throat": "^6.0.1" } }, "jest-runtime": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.3.1.tgz", - "integrity": "sha512-qtO6VxPbS8umqhEDpjA4pqTkKQ1Hy4ZSi9mDVeE9Za7LKBo2LdW2jmT+Iod3XFaJqINikZQsn2wEi0j9wPRbLg==", - "dev": true, - "requires": { - "@jest/console": "^27.3.1", - "@jest/environment": "^27.3.1", - "@jest/globals": "^27.3.1", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.3.1", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", - "@types/yargs": "^16.0.0", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", + "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", + "dev": true, + "requires": { + "@jest/environment": "^27.4.6", + "@jest/fake-timers": "^27.4.6", + "@jest/globals": "^27.4.6", + "@jest/source-map": "^27.4.0", + "@jest/test-result": "^27.4.6", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "execa": "^5.0.0", - "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-mock": "^27.3.0", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.3.1", - "jest-snapshot": "^27.3.1", - "jest-util": "^27.3.1", - "jest-validate": "^27.3.1", + "jest-haste-map": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-mock": "^27.4.6", + "jest-regex-util": "^27.4.0", + "jest-resolve": "^27.4.6", + "jest-snapshot": "^27.4.6", + "jest-util": "^27.4.2", "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.2.0" + "strip-bom": "^4.0.0" } }, "jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", + "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", "dev": true, "requires": { "@types/node": "*", @@ -10810,44 +10826,42 @@ } }, "jest-snapshot": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.3.1.tgz", - "integrity": "sha512-APZyBvSgQgOT0XumwfFu7X3G5elj6TGhCBLbBdn3R1IzYustPGPE38F51dBWMQ8hRXa9je0vAdeVDtqHLvB6lg==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", + "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", "dev": true, "requires": { "@babel/core": "^7.7.2", "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/transform": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/transform": "^27.4.6", + "@jest/types": "^27.4.2", "@types/babel__traverse": "^7.0.4", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.3.1", + "expect": "^27.4.6", "graceful-fs": "^4.2.4", - "jest-diff": "^27.3.1", - "jest-get-type": "^27.3.1", - "jest-haste-map": "^27.3.1", - "jest-matcher-utils": "^27.3.1", - "jest-message-util": "^27.3.1", - "jest-resolve": "^27.3.1", - "jest-util": "^27.3.1", + "jest-diff": "^27.4.6", + "jest-get-type": "^27.4.0", + "jest-haste-map": "^27.4.6", + "jest-matcher-utils": "^27.4.6", + "jest-message-util": "^27.4.6", + "jest-util": "^27.4.2", "natural-compare": "^1.4.0", - "pretty-format": "^27.3.1", + "pretty-format": "^27.4.6", "semver": "^7.3.2" } }, "jest-util": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.3.1.tgz", - "integrity": "sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", + "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -10856,46 +10870,46 @@ } }, "jest-validate": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.3.1.tgz", - "integrity": "sha512-3H0XCHDFLA9uDII67Bwi1Vy7AqwA5HqEEjyy934lgVhtJ3eisw6ShOF1MDmRPspyikef5MyExvIm0/TuLzZ86Q==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", + "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.3.1", + "jest-get-type": "^27.4.0", "leven": "^3.1.0", - "pretty-format": "^27.3.1" + "pretty-format": "^27.4.6" }, "dependencies": { "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true } } }, "jest-watcher": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.3.1.tgz", - "integrity": "sha512-9/xbV6chABsGHWh9yPaAGYVVKurWoP3ZMCv6h+O1v9/+pkOroigs6WzZ0e9gLP/njokUwM7yQhr01LKJVMkaZA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", + "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", "dev": true, "requires": { - "@jest/test-result": "^27.3.1", - "@jest/types": "^27.2.5", + "@jest/test-result": "^27.4.6", + "@jest/types": "^27.4.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.3.1", + "jest-util": "^27.4.2", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", - "integrity": "sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", + "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", "dev": true, "requires": { "@types/node": "*", @@ -10965,9 +10979,9 @@ }, "dependencies": { "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true } } @@ -11028,9 +11042,9 @@ "dev": true }, "klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", "dev": true }, "leven": { @@ -11056,9 +11070,9 @@ "dev": true }, "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "dev": true, "requires": { "big.js": "^5.2.2", @@ -11081,12 +11095,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11155,12 +11163,12 @@ "dev": true }, "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "requires": { - "tmpl": "1.0.x" + "tmpl": "1.0.5" } }, "merge-stream": { @@ -11186,18 +11194,18 @@ } }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", "dev": true }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "dev": true, "requires": { - "mime-db": "1.50.0" + "mime-db": "1.51.0" } }, "mimic-fn": { @@ -11239,9 +11247,9 @@ "dev": true }, "nanoid": { - "version": "3.1.30", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "natural-compare": { @@ -11282,16 +11290,10 @@ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", "dev": true }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, "node-releases": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz", - "integrity": "sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, "normalize-path": { @@ -11330,9 +11332,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" }, "once": { "version": "1.4.0", @@ -11473,19 +11475,16 @@ "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "dev": true }, "pkg-dir": { "version": "4.2.0", @@ -11497,22 +11496,14 @@ } }, "postcss": { - "version": "8.3.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz", - "integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==", + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", + "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "dev": true, "requires": { - "nanoid": "^3.1.28", - "picocolors": "^0.2.1", - "source-map-js": "^0.6.2" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - } + "nanoid": "^3.1.30", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.1" } }, "postcss-modules-extract-imports": { @@ -11552,9 +11543,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", + "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -11562,9 +11553,9 @@ } }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "prelude-ls": { @@ -11574,9 +11565,9 @@ "dev": true }, "prettier": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", "dev": true }, "prettier-linter-helpers": { @@ -11589,22 +11580,21 @@ } }, "pretty-error": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-3.0.4.tgz", - "integrity": "sha512-ytLFLfv1So4AO1UkoBF6GXQgJRaKbiSiGFICaOPNwQ3CMvBvXpLRubeQWyPGnsbV/t9ml9qto6IeCsho0aEvwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "requires": { "lodash": "^4.17.20", - "renderkid": "^2.0.6" + "renderkid": "^3.0.0" } }, "pretty-format": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", - "integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==", + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", + "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", "dev": true, "requires": { - "@jest/types": "^27.2.5", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" @@ -11635,13 +11625,13 @@ } }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" }, "dependencies": { "react-is": { @@ -11664,9 +11654,9 @@ "dev": true }, "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "requires": { "side-channel": "^1.0.4" } @@ -11708,27 +11698,19 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "react-redux": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz", - "integrity": "sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz", + "integrity": "sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==", "requires": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", "hoist-non-react-statics": "^3.3.2", "loose-envify": "^1.4.0", "prop-types": "^15.7.2", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } + "react-is": "^17.0.2" } }, "readable-stream": { @@ -11760,9 +11742,9 @@ } }, "redux": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz", - "integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", + "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", "requires": { "@babel/runtime": "^7.9.2" } @@ -11785,33 +11767,16 @@ "dev": true }, "renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, "requires": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "strip-ansi": "^6.0.1" } }, "require-directory": { @@ -11827,13 +11792,14 @@ "dev": true }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-cwd": { @@ -11917,12 +11883,14 @@ "dev": true }, "sass": { - "version": "1.43.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.43.2.tgz", - "integrity": "sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz", + "integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==", "dev": true, "requires": { - "chokidar": ">=3.0.0 <4.0.0" + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" } }, "sass-loader": { @@ -12017,9 +11985,9 @@ } }, "signal-exit": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, "sisteransi": { @@ -12063,15 +12031,15 @@ "dev": true }, "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -12101,15 +12069,6 @@ } } }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12192,6 +12151,12 @@ "supports-color": "^7.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -12199,13 +12164,12 @@ "dev": true }, "table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "requires": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", @@ -12213,9 +12177,9 @@ }, "dependencies": { "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -12248,45 +12212,33 @@ "supports-hyperlinks": "^2.0.0" } }, - "terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", + "terser-webpack-plugin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", + "integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==", "dev": true, "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" + "jest-worker": "^27.4.1", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" }, "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "optional": true, + "peer": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, - "requires": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "dependencies": { "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -12295,6 +12247,25 @@ "requires": { "randombytes": "^2.1.0" } + }, + "terser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } } } }, @@ -12363,9 +12334,9 @@ } }, "ts-jest": { - "version": "27.0.7", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.7.tgz", - "integrity": "sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q==", + "version": "27.1.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", + "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", "dev": true, "requires": { "bs-logger": "0.x", @@ -12391,9 +12362,9 @@ } }, "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", + "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -12420,9 +12391,9 @@ } }, "tsconfig-paths-webpack-plugin": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.1.tgz", - "integrity": "sha512-n5CMlUUj+N5pjBhBACLq4jdr9cPTitySCjIosoQm0zwK99gmrcTGAfY9CwxRFT9+9OleNWXPRUcxsKP4AYExxQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -12484,9 +12455,9 @@ } }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, "universalify": { @@ -12522,9 +12493,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", - "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -12559,18 +12530,18 @@ } }, "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "requires": { - "makeerror": "1.0.x" + "makeerror": "1.0.12" } }, "watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", + "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -12584,9 +12555,9 @@ "dev": true }, "webpack": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.59.0.tgz", - "integrity": "sha512-2HiFHKnWIb/cBfOfgssQn8XIRvntISXiz//F1q1+hKMs+uzC1zlVCJZEP7XqI1wzrDyc/ZdB4G+MYtz5biJxCA==", + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", + "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", @@ -12603,7 +12574,7 @@ "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "json-parse-better-errors": "^1.0.2", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -12611,14 +12582,14 @@ "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.2" }, "dependencies": { "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-import-assertions": { @@ -12629,9 +12600,9 @@ "requires": {} }, "webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true } } @@ -12761,9 +12732,9 @@ } }, "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index f0e97c95..77ffe039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.3.1", + "version": "3.3.2", "private": true, "contributors": [ "Suphanat Chunhapanya ", @@ -29,8 +29,7 @@ "react-dom": "^17.0.2", "react-redux": "^7.2.5", "redux": "^4.1.1", - "sjcl": "^1.0.8", - "stream-browserify": "^3.0.0" + "sjcl": "^1.0.8" }, "devDependencies": { "@types/chrome": "^0.0.159", diff --git a/public/manifest.json b/public/manifest.json index 7909b6e7..ac7c619d 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.3.1", + "version": "3.3.2", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/tsconfig.json b/src/background/tsconfig.json index 0ea43e49..147b5df7 100644 --- a/src/background/tsconfig.json +++ b/src/background/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "allowJs": true, "lib": [ "dom" ] diff --git a/src/popup/tsconfig.json b/src/popup/tsconfig.json index 745230a4..0adc9e48 100644 --- a/src/popup/tsconfig.json +++ b/src/popup/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "jsx": "react", "baseUrl": ".", "paths": { "@root/*": [ diff --git a/tsconfig.json b/tsconfig.json index 412ec5dc..d17b7e34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "lib": ["ES2020", "dom"], - "module": "ES2020", - "target": "ES2020", + "module": "ESNext", + "target": "ES5", + "downlevelIteration": true, "declaration": true, "declarationMap": true, "sourceMap": true, @@ -10,20 +10,23 @@ "incremental": true, "removeComments": true, "isolatedModules": true, - "strict": true, - "strictNullChecks": true, + "allowJs": true, + "checkJs": false, + "jsx": "react", + "outDir": "lib", + "rootDir": "./", "noEmitOnError": true, + "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "node", + "allowSyntheticDefaultImports": true, "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "allowSyntheticDefaultImports": true, - "rootDir": ".", - "outDir": "lib" + "declarationDir": "lib" }, "files": [], "include": [], diff --git a/webpack.config.js b/webpack.config.js index dd1d5dcf..31dafffa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,20 +4,13 @@ import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; import path from 'path'; +import { fileURLToPath } from 'url'; -const __dirname = (() => { - const filepath_uri = import.meta.url; - const prefix = `file:${path.sep === '/' ? '' : path.sep}`; - let fp; - fp = path.normalize(filepath_uri); - fp = (fp.indexOf(prefix) === 0) ? fp.substring(prefix.length, fp.length) : new URL(filepath_uri).pathname; - fp = path.dirname(fp); - return fp; -})(); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); const tsloader = { - test: /\.tsx?$/, - exclude: /node_modules/, + test: /\.[jt]sx?$/, + exclude: /node_modules(?![\/\\](?:buffer|keccak))/, loader: 'ts-loader', options: { projectReferences: true, @@ -41,14 +34,16 @@ const background = { entry: { background: path.resolve('src/background/index.ts'), }, - externals: { crypto: 'null' }, + externals: { + crypto: 'null', + }, module: { rules: [tsloader], }, resolve: { extensions: ['.tsx', '.ts', '.js'], fallback: { - buffer: 'buffer', + buffer: 'buffer/', }, }, plugins: [ From b9df5386e70b72f29ca6742b381bb34af5107d0d Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Tue, 18 Jan 2022 15:38:35 -0800 Subject: [PATCH 14/36] minor configuration updates change: * name of output directory from 'PrivacyPass' to 'dist/PrivacyPass' * location of crx and xpi build scripts from '.bin' to 'dist/.bin' * location of pem input and crx/xpi output files from '/' to 'dist' --- .gitignore | 15 +++++++++------ README.md | 6 +++--- .../.bin}/chromium/pack_crx3_with_chrome.bat | 0 .../.bin}/chromium/pack_crx3_with_chrome.sh | 0 .../.bin}/chromium/pack_crx3_with_openssl.sh | 0 ...-unsigned-extensions-permanently-to-firefox.md | 0 .../.bin}/firefox/pack_xpi_with_7zip.bat | 0 {.bin => dist/.bin}/firefox/pack_xpi_with_zip.sh | 0 package.json | 2 +- webpack.config.js | 2 +- 10 files changed, 14 insertions(+), 11 deletions(-) rename {.bin => dist/.bin}/chromium/pack_crx3_with_chrome.bat (100%) rename {.bin => dist/.bin}/chromium/pack_crx3_with_chrome.sh (100%) rename {.bin => dist/.bin}/chromium/pack_crx3_with_openssl.sh (100%) rename {.bin => dist/.bin}/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md (100%) rename {.bin => dist/.bin}/firefox/pack_xpi_with_7zip.bat (100%) rename {.bin => dist/.bin}/firefox/pack_xpi_with_zip.sh (100%) diff --git a/.gitignore b/.gitignore index 3ce201dd..fb295cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,12 @@ -*.swp node_modules/ lib/ -PrivacyPass/ -PrivacyPass.pem -PrivacyPass.crx -PrivacyPass.xpi -.bin/**/temp/ +dist/PrivacyPass/ +dist/PrivacyPass.pem +dist/PrivacyPass.crx +dist/PrivacyPass.crx2 +dist/PrivacyPass.crx3 +dist/PrivacyPass.xpi +dist/.bin/**/temp/ + +*.swp diff --git a/README.md b/README.md index 0c26b163..465eedc0 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ $ npm ci $ npm run build ``` -After that, the `PrivacyPass` folder will contain all files required by the extension. +After that, the `dist/PrivacyPass` folder will contain all files required by the extension. ## Development Installation @@ -47,7 +47,7 @@ After that, the `PrivacyPass` folder will contain all files required by the exte - Build by following the [Build Instruction](#build-instruction). - Open Firefox and go to `about:debugging#/runtime/this-firefox`. - Click on 'Load Temporary Add-on' button. -- Select `manifest.json` from `PrivacyPass` folder. +- Select `manifest.json` from `dist/PrivacyPass` folder. - Check extension logo appears in the top-right corner and 0 passes are stored (by clicking on it). - Go to a web page supporting Privacy Pass where internet challenges @@ -68,7 +68,7 @@ After that, the `PrivacyPass` folder will contain all files required by the exte - Open Chrome and go to `chrome://extensions`. - Turn on the Developer mode on the top-right corner. - Click on 'Load unpacked' button. -- Select the `PrivacyPass` folder. +- Select the `dist/PrivacyPass` folder. - Check extension logo appears in the top-right corner and follow the same instruction as in Firefox. (If you cannot see the extension logo, it's probably just not pinned to the toolbar yest) diff --git a/.bin/chromium/pack_crx3_with_chrome.bat b/dist/.bin/chromium/pack_crx3_with_chrome.bat similarity index 100% rename from .bin/chromium/pack_crx3_with_chrome.bat rename to dist/.bin/chromium/pack_crx3_with_chrome.bat diff --git a/.bin/chromium/pack_crx3_with_chrome.sh b/dist/.bin/chromium/pack_crx3_with_chrome.sh similarity index 100% rename from .bin/chromium/pack_crx3_with_chrome.sh rename to dist/.bin/chromium/pack_crx3_with_chrome.sh diff --git a/.bin/chromium/pack_crx3_with_openssl.sh b/dist/.bin/chromium/pack_crx3_with_openssl.sh similarity index 100% rename from .bin/chromium/pack_crx3_with_openssl.sh rename to dist/.bin/chromium/pack_crx3_with_openssl.sh diff --git a/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md b/dist/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md similarity index 100% rename from .bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md rename to dist/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md diff --git a/.bin/firefox/pack_xpi_with_7zip.bat b/dist/.bin/firefox/pack_xpi_with_7zip.bat similarity index 100% rename from .bin/firefox/pack_xpi_with_7zip.bat rename to dist/.bin/firefox/pack_xpi_with_7zip.bat diff --git a/.bin/firefox/pack_xpi_with_zip.sh b/dist/.bin/firefox/pack_xpi_with_zip.sh similarity index 100% rename from .bin/firefox/pack_xpi_with_zip.sh rename to dist/.bin/firefox/pack_xpi_with_zip.sh diff --git a/package.json b/package.json index 77ffe039..38b9a1d5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "pretest": "npm run sjcl", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "lint": "eslint ./src/**/*.{ts,tsx}", - "clean": "rimraf lib && rimraf PrivacyPass && rimraf PrivacyPass.crx && rimraf PrivacyPass.xpi" + "clean": "rimraf lib && rimraf dist/PrivacyPass && rimraf dist/PrivacyPass.crx* && rimraf dist/PrivacyPass.xpi" }, "dependencies": { "asn1-parser": "^1.1.8", diff --git a/webpack.config.js b/webpack.config.js index 31dafffa..87e7c089 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,7 @@ const tsloader = { const common = { output: { - path: path.resolve('PrivacyPass'), + path: path.resolve('dist/PrivacyPass'), }, context: __dirname, mode: 'production', From cf7144c45778b026704d62d595904f23038418d2 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 19 Jan 2022 21:07:42 -0800 Subject: [PATCH 15/36] refactor and improve the dist/.bin build scripts --- .gitignore | 16 ++- dist/.bin/.env/7zip.bat | 5 + dist/.bin/.env/build.bat | 6 + dist/.bin/.env/build.sh | 6 + dist/.bin/.env/chrome_crx2.bat | 10 ++ dist/.bin/.env/chrome_crx2.sh | 10 ++ dist/.bin/.env/chrome_crx3.bat | 16 +++ dist/.bin/.env/chrome_crx3.sh | 16 +++ dist/.bin/.env/constants.bat | 3 + dist/.bin/.env/constants.sh | 3 + dist/.bin/.env/openssl.sh | 5 + dist/.bin/build/build.bat | 12 ++ dist/.bin/build/build.sh | 9 ++ dist/.bin/build_all.bat | 13 +++ dist/.bin/build_all.sh | 10 ++ dist/.bin/firefox/pack_xpi_with_7zip.bat | 15 --- dist/.bin/inject ES6 polyfills/README.md | 6 + .../inject_es6_polyfills.bat | 65 +++++++++++ .../inject_es6_polyfills.sh | 45 ++++++++ dist/.bin/inject ES6 polyfills/lib/core-js.js | 1 + dist/.bin/inject ES6 polyfills/lib/info.txt | 4 + .../.common/pack_crx_with_chrome.bat} | 10 +- .../chromium/.common/pack_crx_with_chrome.sh} | 15 ++- .../chromium/crx2/pack_crx2_with_chrome.bat | 16 +++ .../chromium/crx2/pack_crx2_with_chrome.sh | 13 +++ .../chromium/crx2/pack_crx2_with_openssl.sh | 103 ++++++++++++++++++ .../chromium/crx3/pack_crx3_with_chrome.bat | 16 +++ .../chromium/crx3/pack_crx3_with_chrome.sh | 13 +++ .../chromium/crx3}/pack_crx3_with_openssl.sh | 33 ++++-- ...igned-extensions-permanently-to-firefox.md | 0 .../firefox/pack_xpi_with_7zip.bat | 25 +++++ .../firefox/pack_xpi_with_zip.sh | 12 +- package.json | 6 +- 33 files changed, 487 insertions(+), 51 deletions(-) create mode 100755 dist/.bin/.env/7zip.bat create mode 100755 dist/.bin/.env/build.bat create mode 100755 dist/.bin/.env/build.sh create mode 100755 dist/.bin/.env/chrome_crx2.bat create mode 100755 dist/.bin/.env/chrome_crx2.sh create mode 100755 dist/.bin/.env/chrome_crx3.bat create mode 100755 dist/.bin/.env/chrome_crx3.sh create mode 100755 dist/.bin/.env/constants.bat create mode 100755 dist/.bin/.env/constants.sh create mode 100755 dist/.bin/.env/openssl.sh create mode 100755 dist/.bin/build/build.bat create mode 100755 dist/.bin/build/build.sh create mode 100755 dist/.bin/build_all.bat create mode 100755 dist/.bin/build_all.sh delete mode 100755 dist/.bin/firefox/pack_xpi_with_7zip.bat create mode 100644 dist/.bin/inject ES6 polyfills/README.md create mode 100755 dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat create mode 100755 dist/.bin/inject ES6 polyfills/inject_es6_polyfills.sh create mode 100644 dist/.bin/inject ES6 polyfills/lib/core-js.js create mode 100644 dist/.bin/inject ES6 polyfills/lib/info.txt rename dist/.bin/{chromium/pack_crx3_with_chrome.bat => pack extensions/chromium/.common/pack_crx_with_chrome.bat} (53%) rename dist/.bin/{chromium/pack_crx3_with_chrome.sh => pack extensions/chromium/.common/pack_crx_with_chrome.sh} (61%) create mode 100755 dist/.bin/pack extensions/chromium/crx2/pack_crx2_with_chrome.bat create mode 100755 dist/.bin/pack extensions/chromium/crx2/pack_crx2_with_chrome.sh create mode 100755 dist/.bin/pack extensions/chromium/crx2/pack_crx2_with_openssl.sh create mode 100755 dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.bat create mode 100755 dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.sh rename dist/.bin/{chromium => pack extensions/chromium/crx3}/pack_crx3_with_openssl.sh (79%) rename dist/.bin/{ => pack extensions}/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md (100%) create mode 100755 dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat rename dist/.bin/{ => pack extensions}/firefox/pack_xpi_with_zip.sh (67%) diff --git a/.gitignore b/.gitignore index fb295cbe..a819948c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,10 @@ -node_modules/ -lib/ +/node_modules +/lib -dist/PrivacyPass/ -dist/PrivacyPass.pem -dist/PrivacyPass.crx -dist/PrivacyPass.crx2 -dist/PrivacyPass.crx3 -dist/PrivacyPass.xpi -dist/.bin/**/temp/ +/dist/PrivacyPass/ +/dist/PrivacyPass.pem +/dist/PrivacyPass.crx* +/dist/PrivacyPass.xpi +/dist/.bin/**/temp/ *.swp diff --git a/dist/.bin/.env/7zip.bat b/dist/.bin/.env/7zip.bat new file mode 100755 index 00000000..0840f988 --- /dev/null +++ b/dist/.bin/.env/7zip.bat @@ -0,0 +1,5 @@ +@echo off + +set ZIP7_HOME=C:\PortableApps\7-Zip\16.02\App\7-Zip64 + +set PATH=%ZIP7_HOME%;%PATH% diff --git a/dist/.bin/.env/build.bat b/dist/.bin/.env/build.bat new file mode 100755 index 00000000..7ac1f6bc --- /dev/null +++ b/dist/.bin/.env/build.bat @@ -0,0 +1,6 @@ +@echo off + +set PERL_HOME=C:\PortableApps\perl\5.10.1 +set MAKE_HOME=C:\PortableApps\make + +set PATH=%PERL_HOME%;%MAKE_HOME%;%PATH% diff --git a/dist/.bin/.env/build.sh b/dist/.bin/.env/build.sh new file mode 100755 index 00000000..fb3d1a3b --- /dev/null +++ b/dist/.bin/.env/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +PERL_HOME='/c/PortableApps/perl/5.10.1' +MAKE_HOME='/c/PortableApps/make' + +export PATH="${PERL_HOME}:${MAKE_HOME}:${PATH}" diff --git a/dist/.bin/.env/chrome_crx2.bat b/dist/.bin/.env/chrome_crx2.bat new file mode 100755 index 00000000..cdba0449 --- /dev/null +++ b/dist/.bin/.env/chrome_crx2.bat @@ -0,0 +1,10 @@ +@echo off + +rem :: =============================== +rem :: version of Chrome < 64.0.3242.0 +rem :: =============================== +rem :: https://sourceforge.net/projects/portableapps/files/Iron%20Portable/ +rem :: https://sourceforge.net/projects/portableapps/files/Iron%20Portable/IronPortable_61.0.3200.0.paf.exe/download +set CHROME_HOME=C:\PortableApps\SRWare Iron\61.0.3200.0\App\Iron + +set PATH=%CHROME_HOME%;%PATH% diff --git a/dist/.bin/.env/chrome_crx2.sh b/dist/.bin/.env/chrome_crx2.sh new file mode 100755 index 00000000..14a3d46f --- /dev/null +++ b/dist/.bin/.env/chrome_crx2.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# =============================== +# version of Chrome < 64.0.3242.0 +# =============================== +# https://sourceforge.net/projects/portableapps/files/Iron%20Portable/ +# https://sourceforge.net/projects/portableapps/files/Iron%20Portable/IronPortable_61.0.3200.0.paf.exe/download +CHROME_HOME='/c/PortableApps/SRWare Iron/61.0.3200.0/App/Iron' + +export PATH="${CHROME_HOME}:${PATH}" diff --git a/dist/.bin/.env/chrome_crx3.bat b/dist/.bin/.env/chrome_crx3.bat new file mode 100755 index 00000000..b1a8246e --- /dev/null +++ b/dist/.bin/.env/chrome_crx3.bat @@ -0,0 +1,16 @@ +@echo off + +rem :: ================================ +rem :: version of Chrome >= 64.0.3242.0 +rem :: ================================ +rem :: https://sourceforge.net/projects/portableapps/files/Google%20Chrome%20Portable/ +rem :: https://sourceforge.net/projects/portableapps/files/Google%20Chrome%20Portable/GoogleChromePortable64_97.0.4692.71_online.paf.exe/download +set CHROME_HOME=C:\PortableApps\Google Chrome\97.0.4692.71\App\Chrome-bin +rem :: https://sourceforge.net/projects/portableapps/files/Iron%20Portable/ +rem :: https://sourceforge.net/projects/portableapps/files/Iron%20Portable/IronPortable_85.0.4350.0.paf.exe/download +set CHROME_HOME=C:\PortableApps\SRWare Iron\85.0.4350.0\App\Iron +rem :: http://download1.srware.net/old/ +rem :: http://download1.srware.net/old/iron/win/85/IronPortable64.exe +set CHROME_HOME=C:\PortableApps\SRWare Iron\85.0.4350.0\Iron + +set PATH=%CHROME_HOME%;%PATH% diff --git a/dist/.bin/.env/chrome_crx3.sh b/dist/.bin/.env/chrome_crx3.sh new file mode 100755 index 00000000..905215c7 --- /dev/null +++ b/dist/.bin/.env/chrome_crx3.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# ================================ +# version of Chrome >= 64.0.3242.0 +# ================================ +# https://sourceforge.net/projects/portableapps/files/Google%20Chrome%20Portable/ +# https://sourceforge.net/projects/portableapps/files/Google%20Chrome%20Portable/GoogleChromePortable64_97.0.4692.71_online.paf.exe/download +CHROME_HOME='/c/PortableApps/Google Chrome/97.0.4692.71/App/Chrome-bin' +# https://sourceforge.net/projects/portableapps/files/Iron%20Portable/ +# https://sourceforge.net/projects/portableapps/files/Iron%20Portable/IronPortable_85.0.4350.0.paf.exe/download +CHROME_HOME='/c/PortableApps/SRWare Iron/85.0.4350.0/App/Iron' +# http://download1.srware.net/old/ +# http://download1.srware.net/old/iron/win/85/IronPortable64.exe +CHROME_HOME='/c/PortableApps/SRWare Iron/85.0.4350.0/Iron' + +export PATH="${CHROME_HOME}:${PATH}" diff --git a/dist/.bin/.env/constants.bat b/dist/.bin/.env/constants.bat new file mode 100755 index 00000000..1eae8136 --- /dev/null +++ b/dist/.bin/.env/constants.bat @@ -0,0 +1,3 @@ +@echo off + +set ext_name=PrivacyPass diff --git a/dist/.bin/.env/constants.sh b/dist/.bin/.env/constants.sh new file mode 100755 index 00000000..46ad511c --- /dev/null +++ b/dist/.bin/.env/constants.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +export ext_name='PrivacyPass' diff --git a/dist/.bin/.env/openssl.sh b/dist/.bin/.env/openssl.sh new file mode 100755 index 00000000..01ced177 --- /dev/null +++ b/dist/.bin/.env/openssl.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +OPENSSL_HOME='/c/PortableApps/OpenSSL/1.1.0' + +export PATH="${OPENSSL_HOME}:${PATH}" diff --git a/dist/.bin/build/build.bat b/dist/.bin/build/build.bat new file mode 100755 index 00000000..011d236f --- /dev/null +++ b/dist/.bin/build/build.bat @@ -0,0 +1,12 @@ +@echo off + +call "%~dp0..\.env\%~nx0" + +cd /D "%~dp0..\..\.." + +call npm run build + +if not defined BUILD_ALL ( + echo. + pause +) diff --git a/dist/.bin/build/build.sh b/dist/.bin/build/build.sh new file mode 100755 index 00000000..15cc0d49 --- /dev/null +++ b/dist/.bin/build/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/../.env/build.sh" + +cd "${DIR}/../../.." + +npm run build diff --git a/dist/.bin/build_all.bat b/dist/.bin/build_all.bat new file mode 100755 index 00000000..f9ea539f --- /dev/null +++ b/dist/.bin/build_all.bat @@ -0,0 +1,13 @@ +@echo off + +set BUILD_ALL=1 + +call "%~dp0.\build\build.bat" +call "%~dp0.\pack extensions\chromium\crx3\pack_crx3_with_chrome.bat" +call "%~dp0.\pack extensions\firefox\pack_xpi_with_7zip.bat" + +call "%~dp0.\inject ES6 polyfills\inject_es6_polyfills.bat" +call "%~dp0.\pack extensions\chromium\crx2\pack_crx2_with_chrome.bat" + +echo. +pause diff --git a/dist/.bin/build_all.sh b/dist/.bin/build_all.sh new file mode 100755 index 00000000..0b0602a8 --- /dev/null +++ b/dist/.bin/build_all.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +"${DIR}/build/build.sh" +"${DIR}/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh" +"${DIR}/pack extensions/firefox/pack_xpi_with_zip.sh" + +"${DIR}/inject ES6 polyfills/inject_es6_polyfills.sh" +"${DIR}/pack extensions/chromium/crx2/pack_crx2_with_openssl.sh" diff --git a/dist/.bin/firefox/pack_xpi_with_7zip.bat b/dist/.bin/firefox/pack_xpi_with_7zip.bat deleted file mode 100755 index 6c4f1491..00000000 --- a/dist/.bin/firefox/pack_xpi_with_7zip.bat +++ /dev/null @@ -1,15 +0,0 @@ -@echo off - -set ZIP7_HOME=C:\PortableApps\7-Zip\16.02\App\7-Zip64 -set PATH=%ZIP7_HOME%;%PATH% - -cd /D "%~dp0..\.." - -set ext_name=PrivacyPass -set xpi_file="%cd%\%ext_name%.xpi" - -cd "%ext_name%" - -rem :: https://sevenzip.osdn.jp/chm/cmdline/index.htm -rem :: https://sevenzip.osdn.jp/chm/cmdline/commands/add.htm -7z a -tzip %xpi_file% -r . diff --git a/dist/.bin/inject ES6 polyfills/README.md b/dist/.bin/inject ES6 polyfills/README.md new file mode 100644 index 00000000..49cf612b --- /dev/null +++ b/dist/.bin/inject ES6 polyfills/README.md @@ -0,0 +1,6 @@ +### ES6 Polyfills + +* adds [core-js](https://github.com/zloirock/core-js) to both the [background](../../../public/manifest.json) and [popup](../../../public/popup.html) pages + - using a local copy of a recent browser build, which was saved from [cdnjs](https://cdnjs.com/libraries/core-js) +* only needed to add support for very old browsers, which do not understand features that have since been added to the javascript (aka: ecmascript) scripting language + - for Chrome, this is only recommended for extensions packed in CRX2 format diff --git a/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat new file mode 100755 index 00000000..4ea3cb24 --- /dev/null +++ b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat @@ -0,0 +1,65 @@ +@echo off +setlocal enabledelayedexpansion + +call "%~dp0..\.env\constants.bat" +call "%~dp0..\.env\build.bat" + +if not defined ext_name ( + echo script configuration is invalid: + echo missing name of browser extension + exit /b 1 +) + +cd "%~dp0..\..\%ext_name%" + +if exist "%cd%\lib" ( + echo "lib" directory already exists in extension directory + echo has polyfill has already been injected? + echo quitting without making any changes + exit /b 1 +) + +xcopy /E /I /Q "%~dp0.\lib" "lib" + +set filepath=manifest.json +set "old_text="background.js"" +set "new_text="lib/core-js.js", "background.js"" +set flags= +call :perform_file_search_replace "%filepath%" "!old_text!" "!new_text!" "%flags%" + +set filepath=popup.html +set "old_text=o;)a(n,e=r[o++])&&(~c(i,e)||s(i,e));return i}},function(t,r,e){var c=e(11),f=e(57),s=e(59),e=function(u){return function(t,r,e){var n,o=c(t),i=s(o),a=f(e,i);if(u&&r!=r){for(;a"+t+"<\/script>"},g=function(){var t,r;try{i=new ActiveXObject("htmlfile")}catch(t){}for(g="undefined"==typeof document||document.domain&&i?o(i):((t=l("iframe")).style.display="none",s.appendChild(t),t.src=String("javascript:"),(t=t.contentWindow.document).open(),t.write(p("document.F=Object")),t.close(),t.F),r=c.length;r--;)delete g.prototype[c[r]];return g()};f[h]=!0,t.exports=Object.create||function(t,r){var e;return null!==t?(n.prototype=a(t),e=new n,n.prototype=null,e[h]=t):e=g(),r===Bt?e:u.f(e,r)}},function(t,r,e){var n=e(5),o=e(43),u=e(42),c=e(44),f=e(11),s=e(71);r.f=n&&!o?Object.defineProperties:function(t,r){var e,n,o,i,a;for(c(t),e=f(r),o=(n=s(r)).length,i=0;id)throw m(y);for(r=0;r=r.length?{value:t.target=Bt,done:!0}:"keys"==e?{value:n,done:!1}:"values"==e?{value:r[n],done:!1}:{value:[n,r[n]],done:!1}},"values"),i=i.Arguments=i.Array,o("keys"),o("values"),o("entries"),!f&&e&&"values"!==i.name)try{u(i,"name",{value:"values"})}catch(t){}},function(t,r,e){function d(){return this}var y=e(2),m=e(7),b=e(33),n=e(51),x=e(19),w=e(148),E=e(112),A=e(102),S=e(80),R=e(41),I=e(45),o=e(31),T=e(116),e=e(149),O=n.PROPER,M=n.CONFIGURABLE,P=e.IteratorPrototype,j=e.BUGGY_SAFARI_ITERATORS,k=o("iterator"),_="values",N="entries";t.exports=function(t,r,e,n,o,i,a){var u,c,f,s,l,h,p,g,v;if(w(e,r,n),c=r+" Iterator",f=!(u=function(t){if(t===o&&h)return h;if(!j&&t in s)return s[t];switch(t){case"keys":case _:case N:return function(){return new e(this,t)}}return function(){return new e(this)}}),l=(s=t.prototype)[k]||s["@@iterator"]||o&&s[o],h=!j&&l||u(o),(n="Array"==r&&s.entries||l)&&(p=E(n.call(new t)))!==Object.prototype&&p.next&&(b||E(p)===P||(A?A(p,P):x(p[k])||I(p,k,d)),S(p,c,!0,!0),b&&(T[c]=d)),O&&o==_&&l&&l.name!==_&&(!b&&M?R(s,"name",_):(f=!0,h=function(){return m(l,this)})),o)if(g={values:u(_),keys:i?h:u("keys"),entries:u(N)},a)for(v in g)!j&&!f&&v in s||I(s,v,g[v]);else y({target:r,proto:!0,forced:j||f},g);return b&&!a||s[k]===h||I(s,k,h,{name:o}),T[r]=h,g}},function(t,r,e){function o(){return this}var i=e(149).IteratorPrototype,a=e(69),u=e(10),c=e(80),f=e(116);t.exports=function(t,r,e,n){r+=" Iterator";return t.prototype=a(i,{next:u(+!n,e)}),c(t,r,!1,!0),f[r]=o,t}},function(t,r,e){var n,o=e(6),i=e(19),a=e(69),u=e(112),c=e(45),f=e(31),s=e(33),l=f("iterator"),e=!1;[].keys&&("next"in(f=[].keys())?(f=u(u(f)))!==Object.prototype&&(n=f):e=!0),n==Bt||o(function(){var t={};return n[l].call(t)!==t})?n={}:s&&(n=a(n)),i(n[l])||c(n,l,function(){return this}),t.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:e}},function(t,r,e){var n=e(2),o=e(13),i=e(12),a=e(11),e=e(128),u=o([].join),i=i!=Object,e=e("join",",");n({target:"Array",proto:!0,forced:i||!e},{join:function(t){return u(a(this),t===Bt?",":t)}})},function(t,r,e){var n=e(2),e=e(152);n({target:"Array",proto:!0,forced:e!==[].lastIndexOf},{lastIndexOf:e})},function(t,r,e){var o=e(64),i=e(11),a=e(58),u=e(59),e=e(128),c=Math.min,f=[].lastIndexOf,s=!!f&&1/[1].lastIndexOf(1,-0)<0,e=e("lastIndexOf");t.exports=s||!e?function(t){var r,e,n;if(s)return o(f,this,arguments)||0;for(r=i(this),n=(e=u(r))-1,(n=1s(r)?1:-1})),o=e.length,i=0;i0;)t[n]=t[--n];n!==i++&&(t[n]=e)}return t}(t,r):function(t,r,e,n){for(var o=r.length,i=e.length,a=0,u=0;a>8&255]}function i(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]}function a(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]}function u(t){return J(t,23,4)}function c(t){return J(t,52,8)}function f(t,r,e,n){var o=T(e),e=F(t);if(o+r>e.byteLength)throw H(z);return t=F(e.buffer).bytes,r=N(t,e=o+e.byteOffset,e+r),n?r:$(r)}function s(t,r,e,n,o,i){var a,u,c,f,e=T(e),t=F(t);if(e+r>t.byteLength)throw H(z);for(a=F(t.buffer).bytes,u=e+t.byteOffset,c=n(+o),f=0;fp;)(g=h[p++])in Y||w(Y,g,W[g]);V.constructor=Y}P&&M(q)!==D&&P(q,D),D=new x(new Y(2)),v=y(q.setInt8),D.setInt8(0,2147483648),D.setInt8(1,2147483649),!D.getInt8(0)&&D.getInt8(1)||E(q,{setInt8:function(t,r){v(this,t,r<<24>>24)},setUint8:function(t,r){v(this,t,r<<24>>24)}},{unsafe:!0})}else V=(Y=function(t){S(this,V);t=T(t);B(this,{bytes:K(G(t),0),byteLength:t}),m||(this.byteLength=t)}).prototype,q=(x=function(t,r,e){var n;if(S(this,q),S(t,V),n=F(t).byteLength,(r=R(r))<0||nn)throw H("Wrong length");B(this,{buffer:t,byteLength:e,byteOffset:r}),m||(this.buffer=t,this.byteLength=e,this.byteOffset=r)}).prototype,m&&(O(Y,"byteLength"),O(x,"buffer"),O(x,"byteLength"),O(x,"byteOffset")),E(q,{getInt8:function(t){return f(this,1,t)[0]<<24>>24},getUint8:function(t){return f(this,1,t)[0]},getInt16:function(t){t=f(this,2,t,1>16},getUint16:function(t){t=f(this,2,t,1>>0},getFloat32:function(t){return X(f(this,4,t,1>1,f=23===r?g(2,-24)-g(2,-77):0,s=t<0||0===t&&1/t<0?1:0,l=0;for((t=p(t))!=t||t===1/0?(o=t!=t?1:0,n=u):(n=v(d(t)/y),t*(e=g(2,-n))<1&&(n--,e*=2),2<=(t+=1<=n+c?f/e:f*g(2,1-c))*e&&(n++,e/=2),u<=n+c?(o=0,n=u):1<=n+c?(o=(t*e-1)*g(2,r),n+=c):(o=t*g(2,c-1)*g(2,r),n=0));8<=r;)i[l++]=255&o,o/=256,r-=8;for(n=n<>1,u=o-7,c=n-1,n=t[c--],f=127&n;for(n>>=7;0>=-u,u+=r;0r&&(e=f(e,0,r)),o?n+e:e+n)}};t.exports={start:n(!1),end:n(!0)}},function(t,r,e){var n=e(3),o=e(58),i=e(66),a=e(15),u=n.RangeError;t.exports=function(t){var r=i(a(this)),e="",n=o(t);if(n<0||n==1/0)throw u("Wrong number of repetitions");for(;0>>=1)&&(r+=r))1&n&&(e+=r);return e}},function(t,r,e){var n=e(2),o=e(6),i=e(37),a=e(17);n({target:"Date",proto:!0,forced:o(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})})},{toJSON:function(t){var r=i(this),e=a(r,"number");return"number"!=typeof e||isFinite(e)?r.toISOString():null}})},function(t,r,e){var n=e(36),o=e(45),i=e(195),a=e(31)("toPrimitive"),e=Date.prototype;n(e,a)||o(e,a,i)},function(t,r,e){var n=e(3),o=e(44),i=e(30),a=n.TypeError;t.exports=function(t){if(o(this),"string"===t||"default"===t)t="string";else if("number"!==t)throw a("Incorrect hint");return i(this,t)}},function(t,r,e){var n=e(13),o=e(45),e=Date.prototype,i="Invalid Date",a=n(e.toString),u=n(e.getTime);String(new Date(NaN))!=i&&o(e,"toString",function(){var t=u(this);return t==t?a(this):i})},function(t,r,e){function a(t,r){for(var e=i(t,16);e.length>>=0)?31-n(o(t+.5)*i):32}})},function(t,r,e){var n=e(2),o=e(220),e=Math.cosh,i=Math.abs,a=Math.E;n({target:"Math",stat:!0,forced:!e||e(710)===1/0},{cosh:function(t){t=o(i(t)-1)+1;return(t+1/(t*a*a))*(a/2)}})},function(t,r){var e=Math.expm1,n=Math.exp;t.exports=!e||22025.465794806718u||r!=r?e*(1/0):e*r}},function(t,r,e){var n=e(2),e=Math.hypot,c=Math.abs,f=Math.sqrt;n({target:"Math",stat:!0,forced:!!e&&e(1/0,NaN)!==1/0},{hypot:function(t,r){for(var e,n,o=0,i=0,a=arguments.length,u=0;i>>16)*r+t*(e&o>>>16)<<16>>>0)}})},function(t,r,e){e(2)({target:"Math",stat:!0},{log10:e(227)})},function(t,r){var e=Math.log,n=Math.LOG10E;t.exports=Math.log10||function(t){return e(t)*n}},function(t,r,e){e(2)({target:"Math",stat:!0},{log1p:e(213)})},function(t,r,e){var e=e(2),n=Math.log,o=Math.LN2;e({target:"Math",stat:!0},{log2:function(t){return n(t)/o}})},function(t,r,e){e(2)({target:"Math",stat:!0},{sign:e(217)})},function(t,r,e){var n=e(2),o=e(6),i=e(220),a=Math.abs,u=Math.exp,c=Math.E;n({target:"Math",stat:!0,forced:o(function(){return-2e-17!=Math.sinh(-2e-17)})},{sinh:function(t){return a(t=+t)<1?(i(t)-i(-t))/2:(u(t-1)-u(-t-1))*(c/2)}})},function(t,r,e){var n=e(2),o=e(220),i=Math.exp;n({target:"Math",stat:!0},{tanh:function(t){var r=o(t=+t),e=o(-t);return r==1/0?1:e==1/0?-1:(r-e)/(i(t)+i(-t))}})},function(t,r,e){e(80)(Math,"Math",!0)},function(t,r,e){var e=e(2),n=Math.ceil,o=Math.floor;e({target:"Math",stat:!0},{trunc:function(t){return(0i;i++)h(A,a=o[i])&&!h(n,a)&&x(n,a,b(A,a));l(c,"Number",(n.prototype=S).constructor=n)}},function(t,r,e){e=e(13);t.exports=e(1..valueOf)},function(t,r,e){var n=e(13),o=e(15),i=e(66),e=e(238),a=n("".replace),e="["+e+"]",u=RegExp("^"+e+e+"*"),c=RegExp(e+e+"*$"),e=function(r){return function(t){t=i(o(t));return 1&r&&(t=a(t,u,"")),t=2&r?a(t,c,""):t}};t.exports={start:e(1),end:e(2),trim:e(3)}},function(t,r){t.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},function(t,r,e){e(2)({target:"Number",stat:!0},{EPSILON:Math.pow(2,-52)})},function(t,r,e){e(2)({target:"Number",stat:!0},{isFinite:e(241)})},function(t,r,e){var n=e(3).isFinite;t.exports=Number.isFinite||function(t){return"number"==typeof t&&n(t)}},function(t,r,e){e(2)({target:"Number",stat:!0},{isInteger:e(243)})},function(t,r,e){var n=e(18),o=Math.floor;t.exports=Number.isInteger||function(t){return!n(t)&&isFinite(t)&&o(t)===t}},function(t,r,e){e(2)({target:"Number",stat:!0},{isNaN:function(t){return t!=t}})},function(t,r,e){var n=e(2),o=e(243),i=Math.abs;n({target:"Number",stat:!0},{isSafeInteger:function(t){return o(t)&&i(t)<=9007199254740991}})},function(t,r,e){e(2)({target:"Number",stat:!0},{MAX_SAFE_INTEGER:9007199254740991})},function(t,r,e){e(2)({target:"Number",stat:!0},{MIN_SAFE_INTEGER:-9007199254740991})},function(t,r,e){var n=e(2),e=e(249);n({target:"Number",stat:!0,forced:Number.parseFloat!=e},{parseFloat:e})},function(t,r,e){var n=e(3),o=e(6),i=e(13),a=e(66),u=e(237).trim,e=e(238),c=i("".charAt),f=n.parseFloat,n=n.Symbol,s=n&&n.iterator,o=1/f(e+"-0")!=-1/0||s&&!o(function(){f(Object(s))});t.exports=o?function(t){var r=u(a(t)),t=f(r);return 0===t&&"-"==c(r,0)?-0:t}:f},function(t,r,e){var n=e(2),e=e(251);n({target:"Number",stat:!0,forced:Number.parseInt!=e},{parseInt:e})},function(t,r,e){var n=e(3),o=e(6),i=e(13),a=e(66),u=e(237).trim,e=e(238),c=n.parseInt,n=n.Symbol,f=n&&n.iterator,s=/^[+-]?0x/i,l=i(s.exec),o=8!==c(e+"08")||22!==c(e+"0x16")||f&&!o(function(){c(Object(f))});t.exports=o?function(t,r){t=u(a(t));return c(t,r>>>0||(l(s,t)?16:10))}:c},function(t,r,e){var n=e(2),o=e(3),i=e(13),f=e(58),s=e(236),a=e(192),l=e(227),e=e(6),h=o.RangeError,p=o.String,g=o.isFinite,v=Math.abs,d=Math.floor,y=Math.pow,m=Math.round,b=i(1..toExponential),x=i(a),w=i("".slice),E="-6.9000e-11"===b(-69e-12,4)&&"1.25e+0"===b(1.255,2)&&"1.235e+4"===b(12345,3)&&"3e+1"===b(25,0),i=e(function(){b(1,1/0)})&&e(function(){b(1,-1/0)}),e=!e(function(){b(1/0,1/0)})&&!e(function(){b(NaN,1/0)});n({target:"Number",proto:!0,forced:!E||!i||!e},{toExponential:function(t){var r,e,n,o,i,a,u,c=s(this);if(t===Bt)return b(c);if(r=f(t),!g(c))return p(c);if(r<0||20=(2*(t=m(c/u))+1)*u&&(t+=1),t>=y(10,r+1)&&(t/=10,o+=1),p(t)),0!==r&&(n=w(n,0,1)+"."+w(n,1)),a=0===o?(i="+","0"):(i=0u;)(e=o(n,r=i[u++]))!==Bt&&l(a,r,e);return a}})},function(t,r,e){var n=e(2),o=e(6),e=e(73).f;n({target:"Object",stat:!0,forced:o(function(){return!Object.getOwnPropertyNames(1)})},{getOwnPropertyNames:e})},function(t,r,e){var n=e(2),o=e(6),i=e(37),a=e(112),e=e(113);n({target:"Object",stat:!0,forced:o(function(){a(1)}),sham:!e},{getPrototypeOf:function(t){return a(i(t))}})},function(t,r,e){e(2)({target:"Object",stat:!0},{hasOwn:e(36)})},function(t,r,e){e(2)({target:"Object",stat:!0},{is:e(273)})},function(t,r){t.exports=Object.is||function(t,r){return t===r?0!==t||1/t==1/r:t!=t&&r!=r}},function(t,r,e){var n=e(2),e=e(208);n({target:"Object",stat:!0,forced:Object.isExtensible!==e},{isExtensible:e})},function(t,r,e){var n=e(2),o=e(6),i=e(18),a=e(14),u=e(209),c=Object.isFrozen;n({target:"Object",stat:!0,forced:o(function(){c(1)})||u},{isFrozen:function(t){return!i(t)||!(!u||"ArrayBuffer"!=a(t))||!!c&&c(t)}})},function(t,r,e){var n=e(2),o=e(6),i=e(18),a=e(14),u=e(209),c=Object.isSealed;n({target:"Object",stat:!0,forced:o(function(){c(1)})||u},{isSealed:function(t){return!i(t)||!(!u||"ArrayBuffer"!=a(t))||!!c&&c(t)}})},function(t,r,e){var n=e(2),o=e(37),i=e(71);n({target:"Object",stat:!0,forced:e(6)(function(){i(1)})},{keys:function(t){return i(o(t))}})},function(t,r,e){var n=e(2),o=e(5),i=e(259),a=e(37),u=e(16),c=e(112),f=e(4).f;o&&n({target:"Object",proto:!0,forced:i},{__lookupGetter__:function(t){var r,e=a(this),n=u(t);do{if(r=f(e,n))return r.get}while(e=c(e))}})},function(t,r,e){var n=e(2),o=e(5),i=e(259),a=e(37),u=e(16),c=e(112),f=e(4).f;o&&n({target:"Object",proto:!0,forced:i},{__lookupSetter__:function(t){var r,e=a(this),n=u(t);do{if(r=f(e,n))return r.set}while(e=c(e))}})},function(t,r,e){var n=e(2),o=e(18),i=e(207).onFreeze,a=e(210),e=e(6),u=Object.preventExtensions;n({target:"Object",stat:!0,forced:e(function(){u(1)}),sham:!a},{preventExtensions:function(t){return u&&o(t)?u(i(t)):t}})},function(t,r,e){var n=e(2),o=e(18),i=e(207).onFreeze,a=e(210),e=e(6),u=Object.seal;n({target:"Object",stat:!0,forced:e(function(){u(1)}),sham:!a},{seal:function(t){return u&&o(t)?u(i(t)):t}})},function(t,r,e){e(2)({target:"Object",stat:!0},{setPrototypeOf:e(102)})},function(t,r,e){var n=e(68),o=e(45),e=e(284);n||o(Object.prototype,"toString",e,{unsafe:!0})},function(t,r,e){var n=e(68),o=e(67);t.exports=n?{}.toString:function(){return"[object "+o(this)+"]"}},function(t,r,e){var n=e(2),o=e(264).values;n({target:"Object",stat:!0},{values:function(t){return o(t)}})},function(t,r,e){var n=e(2),e=e(249);n({global:!0,forced:parseFloat!=e},{parseFloat:e})},function(t,r,e){var n=e(2),e=e(251);n({global:!0,forced:parseInt!=e},{parseInt:e})},function(t,r,e){function i(t,r){var e,n,o,i,a=r.value,u=1==r.state,c=u?t.ok:t.fail,f=t.resolve,s=t.reject,l=t.domain;try{c?(u||(2===r.rejection&&(i=r,p(T,h,function(){var t=i.facade;L?$.emit("rejectionHandled",t):ot("rejectionhandled",t,i.value)})),r.rejection=1),!0===c?e=a:(l&&l.enter(),e=c(a),l&&(l.exit(),o=!0)),e===t.promise?s(H("Promise-chain cycle")):(n=et(e))?p(n,e,f,s):f(e)):s(a)}catch(t){l&&!o&&l.exit(),s(t)}}var n,o,a,u,c=e(2),f=e(33),h=e(3),s=e(21),p=e(7),l=e(289),g=e(45),v=e(175),d=e(102),y=e(80),m=e(168),b=e(28),x=e(19),w=e(18),E=e(176),A=e(46),S=e(114),R=e(142),I=e(182),T=e(290).set,O=e(292),M=e(295),P=e(297),j=e(296),k=e(298),_=e(299),N=e(47),U=e(63),D=e(31),C=e(300),L=e(157),F=e(25),B=D("species"),z="Promise",W=N.getterFor(z),Y=N.set,V=N.getterFor(z),N=l&&l.prototype,q=l,G=N,H=h.TypeError,K=h.document,$=h.process,J=j.f,X=J,Q=!!(K&&K.createEvent&&h.dispatchEvent),Z=x(h.PromiseRejectionEvent),tt="unhandledrejection",rt=!1,U=U(z,function(){var t,r=A(q),e=r!==String(q);return!e&&66===F||!(!f||G.finally)||!(51<=F&&/native code/.test(r))&&(t=function(t){t(function(){},function(){})},((r=new q(function(t){t(1)})).constructor={})[B]=t,!(rt=r.then(function(){})instanceof t)||!e&&C&&!Z)}),R=U||!R(function(t){q.all(t).catch(function(){})}),et=function(t){var r;return!(!w(t)||!x(r=t.then))&&r},nt=function(e,o){e.notified||(e.notified=!0,O(function(){for(var t,n,r=e.reactions;t=r.get();)i(t,e);e.notified=!1,o&&!e.rejection&&(n=e,p(T,h,function(){var t,r=n.facade,e=n.value;if(it(n)&&(t=k(function(){L?$.emit("unhandledRejection",e,r):ot(tt,r,e)}),n.rejection=L||it(n)?2:1,t.error))throw t.value}))}))},ot=function(t,r,e){var n,o;Q?((n=K.createEvent("Event")).promise=r,n.reason=e,n.initEvent(t,!1,!0),h.dispatchEvent(n)):n={promise:r,reason:e},!Z&&(o=h["on"+t])?o(n):t===tt&&P("Unhandled promise rejection",e)},it=function(t){return 1!==t.rejection&&!t.parent},at=function(r,e,n){return function(t){r(e,t,n)}},ut=function(t,r,e){t.done||(t.done=!0,(t=e?e:t).value=r,t.state=2,nt(t,!0))},ct=function(e,t,r){if(!e.done){e.done=!0,r&&(e=r);try{if(e.facade===t)throw H("Promise can't be resolved itself");var n=et(t);n?O(function(){var r={done:!1};try{p(n,t,at(ct,r,e),at(ut,r,e))}catch(t){ut(r,t,e)}}):(e.value=t,e.state=1,nt(e,!1))}catch(t){ut({done:!1},t,e)}}};if(U&&(q=function(t){E(this,G),b(t),p(n,this);var r=W(this);try{t(at(ct,r),at(ut,r))}catch(t){ut(r,t)}},(n=function(t){Y(this,{type:z,done:!1,notified:!1,parent:!1,reactions:new _,rejection:!1,state:0,value:Bt})}).prototype=v(G=q.prototype,{then:function(t,r){var e=V(this),n=J(I(this,q));return e.parent=!0,n.ok=!x(t)||t,n.fail=x(r)&&r,n.domain=L?$.domain:Bt,0==e.state?e.reactions.add(n):O(function(){i(n,e)}),n.promise},catch:function(t){return this.then(Bt,t)}}),o=function(){var t=new n,r=W(t);this.promise=t,this.resolve=at(ct,r),this.reject=at(ut,r)},j.f=J=function(t){return t===q||t===a?new o:X(t)},!f&&x(l)&&N!==Object.prototype)){u=N.then,rt||(g(N,"then",function(t,r){var e=this;return new q(function(t,r){p(u,e,t,r)}).then(t,r)},{unsafe:!0}),g(N,"catch",G.catch,{unsafe:!0}));try{delete N.constructor}catch(t){}d&&d(N,G)}c({global:!0,wrap:!0,forced:U},{Promise:q}),y(q,z,!1,!0),m(z),a=s(z),c({target:z,stat:!0,forced:U},{reject:function(t){var r=J(this);return p(r.reject,Bt,t),r.promise}}),c({target:z,stat:!0,forced:f||U},{resolve:function(t){return M(f&&this===a?q:this,t)}}),c({target:z,stat:!0,forced:R},{all:function(t){var u=this,r=J(u),c=r.resolve,f=r.reject,e=k(function(){var n=b(u.resolve),o=[],i=0,a=1;S(t,function(t){var r=i++,e=!1;a++,p(n,u,t).then(function(t){e||(e=!0,o[r]=t,--a||c(o))},f)}),--a||c(o)});return e.error&&f(e.value),r.promise},race:function(t){var e=this,n=J(e),o=n.reject,r=k(function(){var r=b(e.resolve);S(t,function(t){p(r,e,t).then(n.resolve,o)})});return r.error&&o(r.value),n.promise}})},function(t,r,e){e=e(3);t.exports=e.Promise},function(t,r,e){var n,o,i,a,u,c=e(3),f=e(64),s=e(82),l=e(19),h=e(36),p=e(6),g=e(72),v=e(76),d=e(40),y=e(291),m=e(157),b=c.setImmediate,x=c.clearImmediate,w=c.process,E=c.Dispatch,A=c.Function,S=c.MessageChannel,R=c.String,I=0,T={};try{n=c.location}catch(t){}i=function(t){var r;h(T,t)&&(r=T[t],delete T[t],r())},a=function(t){return function(){i(t)}},u=function(t){i(t.data)},e=function(t){c.postMessage(R(t),n.protocol+"//"+n.host)},b&&x||(b=function(t){var r=v(arguments,1);return T[++I]=function(){f(l(t)?t:A(t),Bt,r)},o(I),I},x=function(t){delete T[t]},m?o=function(t){w.nextTick(a(t))}:E&&E.now?o=function(t){E.now(a(t))}:S&&!y?(S=(y=new S).port2,y.port1.onmessage=u,o=s(S.postMessage,S)):c.addEventListener&&l(c.postMessage)&&!c.importScripts&&n&&"file:"!==n.protocol&&!p(e)?(o=e,c.addEventListener("message",u,!1)):o="onreadystatechange"in d("script")?function(t){g.appendChild(d("script")).onreadystatechange=function(){g.removeChild(this),i(t)}}:function(t){setTimeout(a(t),0)}),t.exports={set:b,clear:x}},function(t,r,e){e=e(26);t.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(e)},function(t,r,e){var n,o,i,a,u,c,f,s=e(3),l=e(82),h=e(4).f,p=e(290).set,g=e(291),v=e(293),d=e(294),y=e(157),m=s.MutationObserver||s.WebKitMutationObserver,b=s.document,x=s.process,e=s.Promise,h=h(s,"queueMicrotask"),h=h&&h.value;h||(n=function(){var t,r;for(y&&(t=x.domain)&&t.exit();o;){r=o.fn,o=o.next;try{r()}catch(t){throw o?a():i=Bt,t}}i=Bt,t&&t.enter()},a=g||y||d||!m||!b?!v&&e&&e.resolve?((v=e.resolve(Bt)).constructor=e,f=l(v.then,v),function(){f(n)}):y?function(){x.nextTick(n)}:(p=l(p,s),function(){p(n)}):(u=!0,c=b.createTextNode(""),new m(n).observe(c,{characterData:!0}),function(){c.data=u=!u})),t.exports=h||function(t){t={fn:t,next:Bt};i&&(i.next=t),o||(o=t,a()),i=t}},function(t,r,e){var n=e(26),e=e(3);t.exports=/ipad|iphone|ipod/i.test(n)&&e.Pebble!==Bt},function(t,r,e){e=e(26);t.exports=/web0s(?!.*chrome)/i.test(e)},function(t,r,e){var n=e(44),o=e(18),i=e(296);t.exports=function(t,r){return n(t),o(r)&&r.constructor===t?r:((0,(t=i.f(t)).resolve)(r),t.promise)}},function(t,r,e){function n(t){var e,n;this.promise=new t(function(t,r){if(e!==Bt||n!==Bt)throw TypeError("Bad Promise constructor");e=t,n=r}),this.resolve=o(e),this.reject=o(n)}var o=e(28);t.exports.f=function(t){return new n(t)}},function(t,r,e){var n=e(3);t.exports=function(t,r){var e=n.console;e&&e.error&&(1==arguments.length?e.error(t):e.error(t,r))}},function(t,r){t.exports=function(t){try{return{error:!1,value:t()}}catch(t){return{error:!0,value:t}}}},function(t,r){function e(){this.head=null,this.tail=null}e.prototype={add:function(t){t={item:t,next:null};this.head?this.tail.next=t:this.head=t,this.tail=t},get:function(){var t=this.head;if(t)return this.head=t.next,this.tail===t&&(this.tail=null),t.item}},t.exports=e},function(t,r){t.exports="object"==typeof window},function(t,r,e){var n=e(2),f=e(7),s=e(28),o=e(296),i=e(298),l=e(114);n({target:"Promise",stat:!0},{allSettled:function(t){var u=this,r=o.f(u),c=r.resolve,e=r.reject,n=i(function(){var n=s(u.resolve),o=[],i=0,a=1;l(t,function(t){var r=i++,e=!1;a++,f(n,u,t).then(function(t){e||(e=!0,o[r]={status:"fulfilled",value:t},--a||c(o))},function(t){e||(e=!0,o[r]={status:"rejected",reason:t},--a||c(o))})}),--a||c(o)});return n.error&&e(n.value),r.promise}})},function(t,r,e){var n=e(2),h=e(28),o=e(21),p=e(7),i=e(296),a=e(298),g=e(114),v="No one promise resolved";n({target:"Promise",stat:!0},{any:function(t){var c=this,f=o("AggregateError"),r=i.f(c),s=r.resolve,l=r.reject,e=a(function(){var n=h(c.resolve),o=[],i=0,a=1,u=!1;g(t,function(t){var r=i++,e=!1;a++,p(n,c,t).then(function(t){e||u||(u=!0,s(t))},function(t){e||u||(e=!0,o[r]=t,--a||l(new f(o,v)))})}),--a||l(new f(o,v))});return e.error&&l(e.value),r.promise}})},function(t,r,e){var n=e(2),o=e(33),i=e(289),a=e(6),u=e(21),c=e(19),f=e(182),s=e(295),e=e(45);n({target:"Promise",proto:!0,real:!0,forced:!!i&&a(function(){i.prototype.finally.call({then:function(){}},function(){})})},{finally:function(r){var e=f(this,u("Promise")),t=c(r);return this.then(t?function(t){return s(e,r()).then(function(){return t})}:r,t?function(t){return s(e,r()).then(function(){throw t})}:r)}}),!o&&c(i)&&(o=u("Promise").prototype.finally,i.prototype.finally!==o&&e(i.prototype,"finally",o,{unsafe:!0}))},function(t,r,e){var n=e(2),o=e(64),i=e(28),a=e(44);n({target:"Reflect",stat:!0,forced:!e(6)(function(){Reflect.apply(function(){})})},{apply:function(t,r,e){return o(i(t),r,a(e))}})},function(t,r,e){var n=e(2),o=e(21),i=e(64),a=e(199),u=e(183),c=e(44),f=e(18),s=e(69),e=e(6),l=o("Reflect","construct"),h=Object.prototype,p=[].push,g=e(function(){function t(){}return!(l(function(){},[],t)instanceof t)}),v=!e(function(){l(function(){})}),e=g||v;n({target:"Reflect",stat:!0,forced:e,sham:e},{construct:function(t,r){var e,n;if(u(t),c(r),n=arguments.length<3?t:u(arguments[2]),v&&!g)return l(t,r,n);if(t!=n)return e=s(f(n=n.prototype)?n:h),n=i(t,e,r),f(n)?n:e;switch(r.length){case 0:return new t;case 1:return new t(r[0]);case 2:return new t(r[0],r[1]);case 3:return new t(r[0],r[1],r[2]);case 4:return new t(r[0],r[1],r[2],r[3])}return i(p,e=[null],r),new(i(a,t,e))}})},function(t,r,e){var n=e(2),o=e(5),i=e(44),a=e(16),u=e(42);n({target:"Reflect",stat:!0,forced:e(6)(function(){Reflect.defineProperty(u.f({},1,{value:1}),1,{value:2})}),sham:!o},{defineProperty:function(t,r,e){i(t);r=a(r);i(e);try{return u.f(t,r,e),!0}catch(t){return!1}}})},function(t,r,e){var n=e(2),o=e(44),i=e(4).f;n({target:"Reflect",stat:!0},{deleteProperty:function(t,r){var e=i(o(t),r);return!(e&&!e.configurable)&&delete t[r]}})},function(t,r,e){var n=e(2),i=e(7),a=e(18),u=e(44),c=e(309),f=e(4),s=e(112);n({target:"Reflect",stat:!0},{get:function t(r,e){var n,o=arguments.length<3?r:arguments[2];return u(r)===o?r[e]:(n=f.f(r,e))?c(n)?n.value:n.get===Bt?Bt:i(n.get,o):a(r=s(r))?t(r,e,o):Bt}})},function(t,r,e){var n=e(36);t.exports=function(t){return t!==Bt&&(n(t,"value")||n(t,"writable"))}},function(t,r,e){var n=e(2),o=e(5),i=e(44),a=e(4);n({target:"Reflect",stat:!0,sham:!o},{getOwnPropertyDescriptor:function(t,r){return a.f(i(t),r)}})},function(t,r,e){var n=e(2),o=e(44),i=e(112);n({target:"Reflect",stat:!0,sham:!e(113)},{getPrototypeOf:function(t){return i(o(t))}})},function(t,r,e){e(2)({target:"Reflect",stat:!0},{has:function(t,r){return r in t}})},function(t,r,e){var n=e(2),o=e(44),i=e(208);n({target:"Reflect",stat:!0},{isExtensible:function(t){return o(t),i(t)}})},function(t,r,e){e(2)({target:"Reflect",stat:!0},{ownKeys:e(53)})},function(t,r,e){var n=e(2),o=e(21),i=e(44);n({target:"Reflect",stat:!0,sham:!e(210)},{preventExtensions:function(t){i(t);try{var r=o("Object","preventExtensions");return r&&r(t),!0}catch(t){return!1}}})},function(t,r,e){var n=e(2),u=e(7),c=e(44),f=e(18),s=e(309),o=e(6),l=e(42),h=e(4),p=e(112),g=e(10);n({target:"Reflect",stat:!0,forced:o(function(){function t(){}var r=l.f(new t,"a",{configurable:!0});return!1!==Reflect.set(t.prototype,"a",1,r)})},{set:function t(r,e,n){var o,i=arguments.length<4?r:arguments[3],a=h.f(c(r),e);if(!a){if(f(o=p(r)))return t(o,e,n,i);a=g(0)}if(s(a)){if(!1===a.writable||!f(i))return!1;if(o=h.f(i,e)){if(o.get||o.set||!1===o.writable)return!1;o.value=n,l.f(i,e,o)}else l.f(i,e,g(0,n))}else{if((a=a.set)===Bt)return!1;u(a,i,n)}return!0}})},function(t,r,e){var n=e(2),o=e(44),i=e(103),a=e(102);a&&n({target:"Reflect",stat:!0},{setPrototypeOf:function(t,r){o(t),i(r);try{return a(t,r),!0}catch(t){return!1}}})},function(t,r,e){var n=e(2),o=e(3),e=e(80);n({global:!0},{Reflect:{}}),e(o.Reflect,"Reflect",!0)},function(t,r,e){var f,n,o,i,a=e(5),u=e(3),c=e(13),s=e(63),l=e(104),h=e(41),p=e(42).f,g=e(54).f,v=e(22),d=e(320),y=e(66),m=e(321),b=e(322),x=e(45),w=e(6),E=e(36),A=e(47).enforce,S=e(168),R=e(31),I=e(323),T=e(324),O=R("match"),M=u.RegExp,P=M.prototype,j=u.SyntaxError,k=c(m),_=c(P.exec),N=c("".charAt),U=c("".replace),D=c("".indexOf),C=c("".slice),L=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,F=/a/g,B=/a/g,c=new M(F)!==F,z=b.MISSED_STICKY,W=b.UNSUPPORTED_Y;if(s("RegExp",a&&(!c||z||I||T||w(function(){return B[O]=!1,M(F)!=F||M(B)==B||"/a/i"!=M(F,"i")})))){for(f=function(t,r){var e,n,o=v(P,this),i=d(t),a=r===Bt,u=[],c=t;if(!o&&i&&a&&t.constructor===f)return t;if((i||v(P,t))&&(t=t.source,a&&(r="flags"in c?c.flags:k(c))),t=t===Bt?"":y(t),r=r===Bt?"":y(r),c=t,i=r=I&&"dotAll"in F&&(e=!!r&&-1"===r&&c:if(""===s||E(a,s))throw new j("Invalid capture group name");a[s]=!0,c=!(i[i.length]=[s,f]),s="";continue}c?s+=r:o+=r}return[o,i]}(t))[0],u=a[1]),r=l(M(t,r),o?this:P,f),(e||n||u.length)&&(o=A(r),e&&(o.dotAll=!0,o.raw=f(function(t){for(var r,e=t.length,n=0,o="",i=!1;n<=e;n++)"\\"!==(r=N(t,n))?i||"."!==r?("["===r?i=!0:"]"===r&&(i=!1),o+=r):o+="[\\s\\S]":o+=r+N(t,++n);return o}(t),i)),n&&(o.sticky=!0),u.length&&(o.groups=u)),t!==c)try{h(r,"source",""===c?"(?:)":c)}catch(t){}return r},n=function(r){r in f||p(f,r,{configurable:!0,get:function(){return M[r]},set:function(t){M[r]=t}})},o=g(M),i=0;o.length>i;)n(o[i++]);(P.constructor=f).prototype=P,x(u,"RegExp",f)}S("RegExp")},function(t,r,e){var n=e(18),o=e(14),i=e(31)("match");t.exports=function(t){var r;return n(t)&&((r=t[i])!==Bt?!!r:"RegExp"==o(t))}},function(t,r,e){var n=e(44);t.exports=function(){var t=n(this),r="";return t.global&&(r+="g"),t.ignoreCase&&(r+="i"),t.multiline&&(r+="m"),t.dotAll&&(r+="s"),t.unicode&&(r+="u"),t.sticky&&(r+="y"),r}},function(t,r,e){var n=e(6),o=e(3).RegExp,i=n(function(){var t=o("a","y");return t.lastIndex=2,null!=t.exec("abcd")}),e=i||n(function(){return!o("a","y").sticky}),n=i||n(function(){var t=o("^r","gy");return t.lastIndex=2,null!=t.exec("str")});t.exports={BROKEN_CARET:n,MISSED_STICKY:e,UNSUPPORTED_Y:i}},function(t,r,e){var n=e(6),o=e(3).RegExp;t.exports=n(function(){var t=o(".","s");return!(t.dotAll&&t.exec("\n")&&"s"===t.flags)})},function(t,r,e){var n=e(6),o=e(3).RegExp;t.exports=n(function(){var t=o("(?b)","g");return"b"!==t.exec("b").groups.a||"bc"!=="b".replace(t,"$c")})},function(t,r,e){var n=e(3),o=e(5),i=e(323),a=e(14),u=e(42).f,c=e(47).get,f=RegExp.prototype,s=n.TypeError;o&&i&&u(f,"dotAll",{configurable:!0,get:function(){if(this===f)return Bt;if("RegExp"===a(this))return!!c(this).dotAll;throw s("Incompatible receiver, RegExp required")}})},function(t,r,e){var n=e(2),e=e(327);n({target:"RegExp",proto:!0,forced:/./.exec!==e},{exec:e})},function(t,r,e){var g=e(7),n=e(13),v=e(66),d=e(321),o=e(322),i=e(32),y=e(69),m=e(47).get,a=e(323),e=e(324),b=i("native-string-replace","".replace),x=/t/.exec,w=x,E=n("".charAt),A=n("".indexOf),S=n("".replace),R=n("".slice),I=(i=/b*/g,g(x,n=/a/,"a"),g(x,i,"a"),0!==n.lastIndex||0!==i.lastIndex),T=o.BROKEN_CARET,O=/()??/.exec("")[1]!==Bt;(I||O||T||a||e)&&(w=function(t){var r,e,n,o,i,a,u,c,f,s=this,l=m(s),h=v(t),p=l.raw;if(p)return p.lastIndex=s.lastIndex,f=g(w,p,h),s.lastIndex=p.lastIndex,f;if(u=l.groups,c=T&&s.sticky,t=g(d,s),p=s.source,f=0,l=h,c&&(t=S(t,"y",""),-1===A(t,"g")&&(t+="g"),l=R(h,s.lastIndex),0>10),r%1024+56320)}return f(e,"")}})},function(t,r,e){var n=e(2),o=e(13),i=e(337),a=e(15),u=e(66),e=e(338),c=o("".indexOf);n({target:"String",proto:!0,forced:!e("includes")},{includes:function(t){return!!~c(u(a(this)),u(i(t)),1=r.length?{value:Bt,done:!0}:(e=n(r,e),t.index+=e.length,{value:e,done:!1})})},function(t,r,e){var o=e(7),n=e(343),f=e(44),s=e(60),l=e(66),i=e(15),a=e(27),h=e(344),p=e(345);n("match",function(n,u,c){return[function(t){var r=i(this),e=t==Bt?Bt:a(t,n);return e?o(e,t,r):new RegExp(t)[n](l(r))},function(t){var r,e,n,o,i=f(this),a=l(t),t=c(u,i,a);if(t.done)return t.value;if(!i.global)return p(i,a);for(r=i.unicode,e=[],n=i.lastIndex=0;null!==(o=p(i,a));)o=l(o[0]),""===(e[n]=o)&&(i.lastIndex=h(a,s(i.lastIndex),r)),n++;return 0===n?null:e}]})},function(t,r,e){var c,f,s,l,h,p,g,v;e(326),c=e(13),f=e(45),s=e(327),l=e(6),h=e(31),p=e(41),g=h("species"),v=RegExp.prototype,t.exports=function(e,t,r,n){var a,o=h(e),u=!l(function(){var t={};return t[o]=function(){return 7},7!=""[e](t)}),i=u&&!l(function(){var t=!1,r=/a/;return"split"===e&&((r={}).constructor={},r.constructor[g]=function(){return r},r.flags="",r[o]=/./[o]),r.exec=function(){return t=!0,null},r[o](""),!t});u&&i&&!r||(a=c(/./[o]),t=t(o,""[e],function(t,r,e,n,o){var i=c(t),t=r.exec;return t===s||t===v.exec?u&&!o?{done:!0,value:a(r,e,n)}:{done:!0,value:i(e,r,n)}:{done:!1}}),f(String.prototype,e,t[0]),f(v,o,t[1])),n&&p(v[o],"sham",!0)}},function(t,r,e){var n=e(335).charAt;t.exports=function(t,r,e){return r+(e?n(t,r).length:1)}},function(t,r,e){var n=e(3),o=e(7),i=e(44),a=e(19),u=e(14),c=e(327),f=n.TypeError;t.exports=function(t,r){var e=t.exec;if(a(e))return null!==(e=o(e,t,r))&&i(e),e;if("RegExp"===u(t))return o(c,t,r);throw f("RegExp#exec called on incompatible receiver")}},function(t,r,e){function n(t){var r=h(this),e=l(t),n=w(r,RegExp),t=(o=(o=r.flags)===Bt&&g(P,r)&&!("flags"in P)?k(r):o)===Bt?"":l(o),o=new n(n===RegExp?r.source:r,t),n=!!~_(t,"g"),t=!!~_(t,"u");return o.lastIndex=s(r.lastIndex),new D(o,e,n,t)}var o=e(2),i=e(3),a=e(7),u=e(13),c=e(148),f=e(15),s=e(60),l=e(66),h=e(44),p=e(14),g=e(22),v=e(320),d=e(321),y=e(27),m=e(45),b=e(6),x=e(31),w=e(182),E=e(344),A=e(345),S=e(47),R=e(33),I=x("matchAll"),T="RegExp String Iterator",O=S.set,M=S.getterFor(T),P=RegExp.prototype,j=i.TypeError,k=u(d),_=u("".indexOf),N=u("".matchAll),U=!!N&&!b(function(){N("a",/./)}),D=c(function(t,r,e,n){O(this,{type:T,regexp:t,string:r,global:e,unicode:n,done:!1})},"RegExp String",function(){var t,r,e,n=M(this);return n.done?{value:Bt,done:!0}:null===(e=A(t=n.regexp,r=n.string))?{value:Bt,done:n.done=!0}:(n.global?""===l(e[0])&&(t.lastIndex=E(r,s(t.lastIndex),n.unicode)):n.done=!0,{value:e,done:!1})});o({target:"String",proto:!0,forced:U},{matchAll:function(t){var r,e=f(this);if(null!=t){if(v(t)&&(r=l(f("flags"in P?t.flags:k(t))),!~_(r,"g")))throw j("`.matchAll` does not allow non-global regexes");if(U)return N(e,t);if(r=(r=y(t,I))===Bt&&R&&"RegExp"==p(t)?n:r)return a(r,t,e)}else if(U)return N(e,t);return e=l(e),t=new RegExp(t,"g"),R?a(n,t,e):t[I](e)}}),R||I in P||m(P,I,n)},function(t,r,e){var n=e(2),o=e(191).end;n({target:"String",proto:!0,forced:e(348)},{padEnd:function(t){return o(this,t,1")})||!n||s)},function(t,r,e){var n=e(13),o=e(37),h=Math.floor,p=n("".charAt),g=n("".replace),v=n("".slice),d=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,y=/\$([$&'`]|\d{1,2})/g;t.exports=function(i,a,u,c,f,t){var s=u+i.length,l=c.length,r=y;return f!==Bt&&(f=o(f),r=d),g(t,r,function(t,r){var e,n,o;switch(p(r,0)){case"$":return"$";case"&":return i;case"`":return v(a,0,u);case"'":return v(a,s);case"<":e=f[v(r,1,-1)];break;default:if(0==(n=+r))return t;if(lt.length?-1:""===r?e:T(t,r,e)}var n=e(2),o=e(3),v=e(7),i=e(13),d=e(15),y=e(19),m=e(320),b=e(66),x=e(27),a=e(321),w=e(353),u=e(31),E=e(33),A=u("replace"),S=RegExp.prototype,R=o.TypeError,I=i(a),T=i("".indexOf),O=i("".replace),M=i("".slice),P=Math.max;n({target:"String",proto:!0},{replaceAll:function(t,r){var e,n,o,i,a,u,c,f,s=d(this),l=0,h=0,p="";if(null!=t){if((e=m(t))&&(n=b(d("flags"in S?t.flags:I(t))),!~T(n,"g")))throw R("`.replaceAll` does not allow non-global regexes");if(n=x(t,A))return v(n,t,s,r);if(E&&e)return O(b(s),t,r)}for(o=b(s),i=b(t),(a=y(r))||(r=b(r)),c=P(1,u=i.length),l=g(o,i,0);-1!==l;)f=a?b(r(i,l,o)):w(i,o,l,[],Bt,r),p+=M(o,h,l)+f,h=l+u,l=g(o,i,l+c);return h>>0;if(0==f)return[];if(t===Bt)return[c];if(!h(t))return l(p,c,t,f);for(e=[],n=0,o=new RegExp(t.source,(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":"")+"g");(i=l(S,o,c))&&!((a=o.lastIndex)>n&&(M(e,P(c,n,i.index)),1>>0))return[];if(0===h.length)return null===A(n,h)?[h]:[];for(a=i=0,u=[];a=(e=s(t+r,o))?"":c(n,t,e)}})},function(t,r,e){var n=e(2),o=e(237).trim;n({target:"String",proto:!0,forced:e(360)("trim")},{trim:function(){return o(this)}})},function(t,r,e){var n=e(51).PROPER,o=e(6),i=e(238);t.exports=function(t){return o(function(){return!!i[t]()||"​…᠎"!=="​…᠎"[t]()||n&&i[t].name!==t})}},function(t,r,e){var n=e(2),o=e(237).end,i=e(360)("trimEnd"),e=i?function(){return o(this)}:"".trimEnd;n({target:"String",proto:!0,name:"trimEnd",forced:i},{trimEnd:e,trimRight:e})},function(t,r,e){var n=e(2),o=e(237).start,i=e(360)("trimStart"),e=i?function(){return o(this)}:"".trimStart;n({target:"String",proto:!0,name:"trimStart",forced:i},{trimStart:e,trimLeft:e})},function(t,r,e){var n=e(2),o=e(364);n({target:"String",proto:!0,forced:e(365)("anchor")},{anchor:function(t){return o(this,"a","name",t)}})},function(t,r,e){var n=e(13),i=e(15),a=e(66),u=/"/g,c=n("".replace);t.exports=function(t,r,e,n){var o=a(i(t)),t="<"+r;return""!==e&&(t+=" "+e+'="'+c(a(n),u,""")+'"'),t+">"+o+""}},function(t,r,e){var n=e(6);t.exports=function(r){return n(function(){var t=""[r]('"');return t!==t.toLowerCase()||3e)throw z(Z);c=u/s}else c=w(r),a=new W(u=c*s);for(C(t,{buffer:a,byteOffset:i,byteLength:u,length:c,view:new V(a)});ot)throw f("Wrong length");for(;o>>=0,e>>>=0;return(r>>>0)+(n>>>0)+((t&e|(t|e)&~(t+e>>>0))>>>31)|0}})},function(t,r,e){e(2)({target:"Math",stat:!0},{imulh:function(t,r){var e=+t,n=+r,t=65535&e,r=65535&n,e=e>>16,n=n>>16,r=(e*r>>>0)+(t*r>>>16);return e*n+(r>>16)+((t*n>>>0)+(65535&r)>>16)}})},function(t,r,e){e(2)({target:"Math",stat:!0},{isubh:function(t,r,e,n){t>>>=0,e>>>=0;return(r>>>0)-(n>>>0)-((~t&e|~(t^e)&t-e>>>0)>>>31)|0}})},function(t,r,e){e(2)({target:"Math",stat:!0},{RAD_PER_DEG:180/Math.PI})},function(t,r,e){var e=e(2),n=Math.PI/180;e({target:"Math",stat:!0},{radians:function(t){return t*n}})},function(t,r,e){e(2)({target:"Math",stat:!0},{scale:e(525)})},function(t,r,e){var n=e(2),o=e(3),i=e(44),a=e(241),u=e(148),e=e(47),c="Seeded Random Generator",f=e.set,s=e.getterFor(c),l=o.TypeError,h=u(function(t){f(this,{type:c,seed:t%2147483647})},"Seeded Random",function(){var t=s(this);return{value:(1073741823&(t.seed=(1103515245*t.seed+12345)%2147483647))/1073741823,done:!1}});n({target:"Math",stat:!0,forced:!0},{seededPRNG:function(t){t=i(t).seed;if(!a(t))throw l('Math.seededPRNG() argument should have a "seed" field with a finite value.');return new h(t)}})},function(t,r,e){e(2)({target:"Math",stat:!0},{signbit:function(t){return(t=+t)==t&&0==t?1/t==-1/0:t<0}})},function(t,r,e){e(2)({target:"Math",stat:!0},{umulh:function(t,r){var e=+t,n=+r,t=65535&e,r=65535&n,e=e>>>16,n=n>>>16,r=(e*r>>>0)+(t*r>>>16);return e*n+(r>>>16)+((t*n>>>0)+(65535&r)>>>16)}})},function(t,r,e){var n=e(2),o=e(3),i=e(13),a=e(58),u=e(251),c="Invalid number representation",f=o.RangeError,s=o.SyntaxError,l=o.TypeError,h=/^[\da-z]+$/,p=i("".charAt),g=i(h.exec),v=i(1..toString),d=i("".slice);n({target:"Number",stat:!0},{fromString:function(t,r){var e,n=1;if("string"!=typeof t)throw l(c);if(!t.length)throw s(c);if("-"==p(t,0)&&(n=-1,!(t=d(t,1)).length))throw s(c);if((r=r===Bt?10:a(r))<2||36=n.length)return e.object=e.keys=null,{value:Bt,done:!0};if(t=n[e.index++],i(r=e.object,t)){switch(e.mode){case"keys":return{value:t,done:!1};case"values":return{value:r[t],done:!1}}return{value:[t,r[t]],done:!1}}}})},function(t,r,e){var n=e(2),o=e(539);n({target:"Object",stat:!0},{iterateKeys:function(t){return new o(t,"keys")}})},function(t,r,e){var n=e(2),o=e(539);n({target:"Object",stat:!0},{iterateValues:function(t){return new o(t,"values")}})},function(t,r,e){function a(t){this.observer=d(t),this.cleanup=Bt,this.subscriptionObserver=Bt}var n,u,i,o,c=e(2),f=e(3),s=e(7),l=e(5),h=e(168),p=e(28),g=e(19),v=e(85),d=e(44),y=e(18),m=e(176),b=e(42).f,x=e(45),w=e(175),E=e(117),A=e(27),S=e(114),R=e(297),I=e(31),T=e(47),O=I("observable"),M="Observable",e="Subscription",P="SubscriptionObserver",I=T.getterFor,j=T.set,k=I(M),_=I(e),N=I(P),U=f.Array;a.prototype={type:e,clean:function(){var t=this.cleanup;if(t){this.cleanup=Bt;try{t()}catch(t){R(t)}}},close:function(){var t;l||(t=this.subscriptionObserver,this.facade.closed=!0,t&&(t.closed=!0)),this.observer=Bt},isClosed:function(){return this.observer===Bt}},(n=function(r,t){var e,n,o,i=j(this,new a(r));l||(this.closed=!1);try{(e=A(r,"start"))&&s(e,r,this)}catch(t){R(t)}if(!i.isClosed()){r=i.subscriptionObserver=new u(i);try{n=t(r),null!=(o=n)&&(i.cleanup=g(n.unsubscribe)?function(){o.unsubscribe()}:p(n))}catch(t){return void r.error(t)}i.isClosed()&&i.clean()}}).prototype=w({},{unsubscribe:function(){var t=_(this);t.isClosed()||(t.close(),t.clean())}}),l&&b(n.prototype,"closed",{configurable:!0,get:function(){return _(this).isClosed()}}),(u=function(t){j(this,{type:P,subscriptionState:t}),l||(this.closed=!1)}).prototype=w({},{next:function(t){var r,e=N(this).subscriptionState;if(!e.isClosed()){e=e.observer;try{(r=A(e,"next"))&&s(r,e,t)}catch(t){R(t)}}},error:function(t){var r,e,n=N(this).subscriptionState;if(!n.isClosed()){r=n.observer,n.close();try{(e=A(r,"error"))?s(e,r,t):R(t)}catch(t){R(t)}n.clean()}},complete:function(){var t,r,e=N(this).subscriptionState;if(!e.isClosed()){t=e.observer,e.close();try{(r=A(t,"complete"))&&s(r,t)}catch(t){R(t)}e.clean()}}}),l&&b(u.prototype,"closed",{configurable:!0,get:function(){return N(this).subscriptionState.isClosed()}}),w(o=(i=function(t){m(this,o),j(this,{type:M,subscriber:p(t)})}).prototype,{subscribe:function(t){var r=arguments.length;return new n(g(t)?{next:t,error:1=r.length?{value:Bt,done:!0}:(r=f(r,e),t.index+=r.length,{value:{codePoint:c(r,0),position:e},done:!1})});n({target:"String",proto:!0},{codePoints:function(){return new p(a(i(this)))}})},function(t,r,e){e(346)},function(t,r,e){e(354)},function(t,r,e){e(78)("asyncDispose")},function(t,r,e){e(78)("dispose")},function(t,r,e){e(78)("matcher")},function(t,r,e){e(78)("metadata")},function(t,r,e){e(78)("observable")},function(t,r,e){e(78)("patternMatch")},function(t,r,e){e(78)("replaceAll")},function(t,r,e){var i=e(21),a=e(183),u=e(427),n=e(380),o=e(180),c=e(398),f=o.aTypedArrayConstructor;(0,o.exportTypedArrayStaticMethod)("fromAsync",function(r){var e=this,t=arguments.length,n=1?@[\\\]^|]/,J=/[\0\t\n\r #/:<>?@[\\\]^|]/,X=/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g,Q=/[\t\n\r]/g,tt=function(t){var r,e,n,o,i,a,u,c=U(t,".");if(c.length&&""==c[c.length-1]&&c.length--,4<(r=c.length))return t;for(e=[],n=0;n=I(256,5-r))return null}else if(255":1,"`":1}),at=Lt({},it,{"#":1,"?":1,"{":1,"}":1}),ut=Lt({},at,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),ct=function(t,r){var e=l(t,0);return 32=h&&rx((y-p)/(i=n+1)))throw b(m);for(p+=(o-h)*i,h=o,v=0;vy)throw b(m);if(r==h){for(a=p,u=36;!(a<(f=u<=g?1:g+26<=u?26:u-g));)S(s,w(d(f+(c=a-f)%(f=36-f)))),a=x(c/f),u+=36;S(s,w(d(a))),g=function(t,r,e){var n=0;for(t=e?x(t/700):t>>1,t+=x(t/r);455r.key?1:-1}),t.updateURL()},forEach:function(t){for(var r,e=M(this).entries,n=g(t,1 " + exit 1 + fi + + ext_dir=$1 + ext_key=$2 + crx="${ext_dir}.crx2.crx" + name=$(basename "$ext_dir") + pub="${name}.pub" + sig="${name}.sig" + zip="${name}.zip" + + if [ ! -d "$ext_dir" ];then + echo 'error: extension directory path does not exist' + exit 1 + fi + + if [ ! -f "$ext_key" ];then + echo 'error: pem file path does not exist' + exit 1 + fi + + echo "writing ${name}.crx2.crx" + + # preparation: remove previous crx + rm -f "$crx" + + # preparation: remove all previous temporary files in the cwd + rm -f "$pub" "$sig" "$zip" + + # cleanup: remove all temporary files in the cwd + trap 'rm -f "$pub" "$sig" "$zip"' EXIT + + # zip up the crx dir + cwd=$(pwd -P) + (cd "$ext_dir" && zip -qr -9 -X "${cwd}/${zip}" .) + + # signature + openssl sha1 -sha1 -binary -sign "$ext_key" < "$zip" > "$sig" + + # public key + openssl rsa -pubout -outform DER < "$ext_key" > "$pub" 2>/dev/null + + crmagic_hex='4372 3234' # Cr24 + version_hex='0200 0000' # 2 + pub_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$pub" | awk '{print $5}'))) + sig_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$sig" | awk '{print $5}'))) + ( + echo "${crmagic_hex} ${version_hex} ${pub_len_hex} ${sig_len_hex}" | xxd -r -p + cat "$pub" "$sig" "$zip" + ) > "$crx" + + echo 'success: crx2 Chrome extension has been packed' +} + +# ------------------------------------------------------------------------------ +# bootstrap + +function main { + cd "${DIR}/../../../.." + cwd=$(pwd -P) + ext_dir="${cwd}/${ext_name}" + ext_key="${cwd}/${ext_name}.pem" + + TMP="${DIR}/temp" + [ -d "$TMP" ] && rm -rf "$TMP" + mkdir "$TMP" + + cd "$TMP" + pack_crx2 "$ext_dir" "$ext_key" + + # cleanup: remove temporary directory + cd "$DIR" + rm -rf "$TMP" +} + +main diff --git a/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.bat b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.bat new file mode 100755 index 00000000..80e9ed97 --- /dev/null +++ b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.bat @@ -0,0 +1,16 @@ +@echo off + +call "%~dp0..\..\..\.env\constants.bat" +call "%~dp0..\..\..\.env\chrome_crx3.bat" +call "%~dp0..\.common\pack_crx_with_chrome.bat" + +set crx_path=%~dp0..\..\..\..\%ext_name% + +if exist "%crx_path%.crx" ( + ren "%crx_path%.crx" "%ext_name%.crx3.crx" +) + +if not defined BUILD_ALL ( + echo. + pause +) diff --git a/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.sh b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.sh new file mode 100755 index 00000000..40c0710a --- /dev/null +++ b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_chrome.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/../../../.env/constants.sh" +source "${DIR}/../../../.env/chrome_crx3.sh" +source "${DIR}/../.common/pack_crx_with_chrome.sh" + +crx_path="${DIR}/../../../../${ext_name}" + +if [ -f "${crx_path}.crx" ];then + mv "${crx_path}.crx" "${crx_path}.crx3.crx" +fi diff --git a/dist/.bin/chromium/pack_crx3_with_openssl.sh b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh similarity index 79% rename from dist/.bin/chromium/pack_crx3_with_openssl.sh rename to dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh index a5abc7aa..92f32fd8 100755 --- a/dist/.bin/chromium/pack_crx3_with_openssl.sh +++ b/dist/.bin/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh @@ -2,15 +2,18 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -# ------------------------------------------------------------------------------ -# configuration +source "${DIR}/../../../.env/constants.sh" +source "${DIR}/../../../.env/openssl.sh" -OPENSSL_HOME='/c/PortableApps/OpenSSL/1.1.0' -PATH="${OPENSSL_HOME}:${PATH}" +if [ -z "$ext_name" ];then + echo 'script configuration is invalid:' + echo 'missing name of browser extension' + exit 1 +fi # ------------------------------------------------------------------------------ # Source: https://stackoverflow.com/a/18709204 -# Purpose: Pack a Chromium extension directory into crx format +# Purpose: Pack a Chromium extension directory into crx3 format # notes: all temporary files are created in the cwd. # the final crx is created adjacent to the input extension directory. @@ -22,7 +25,7 @@ function pack_crx3 { ext_dir=$1 ext_key=$2 - crx="${ext_dir}.crx" + crx="${ext_dir}.crx3.crx" name=$(basename "$ext_dir") pub="${name}.pub" sig="${name}.sig" @@ -30,7 +33,17 @@ function pack_crx3 { tosign="${name}.presig" binary_crx_id="${name}.crxid" - echo "writing '${name}.crx'" + if [ ! -d "$ext_dir" ];then + echo 'error: extension directory path does not exist' + exit 1 + fi + + if [ ! -f "$ext_key" ];then + echo 'error: pem file path does not exist' + exit 1 + fi + + echo "writing ${name}.crx3.crx" # preparation: remove previous crx rm -f "$crx" @@ -85,10 +98,10 @@ function pack_crx3 { # bootstrap function main { - cd "${DIR}/../.." + cd "${DIR}/../../../.." cwd=$(pwd -P) - ext_dir="${cwd}/PrivacyPass" - ext_key="${cwd}/PrivacyPass.pem" + ext_dir="${cwd}/${ext_name}" + ext_key="${cwd}/${ext_name}.pem" TMP="${DIR}/temp" [ -d "$TMP" ] && rm -rf "$TMP" diff --git a/dist/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md b/dist/.bin/pack extensions/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md similarity index 100% rename from dist/.bin/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md rename to dist/.bin/pack extensions/firefox/docs/installing-unsigned-extensions-permanently-to-firefox.md diff --git a/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat b/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat new file mode 100755 index 00000000..dd966ecd --- /dev/null +++ b/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat @@ -0,0 +1,25 @@ +@echo off + +call "%~dp0..\..\.env\constants.bat" +call "%~dp0..\..\.env\7zip.bat" + +if not defined ext_name ( + echo script configuration is invalid: + echo missing name of browser extension + exit /b 1 +) + +cd /D "%~dp0..\..\.." + +set xpi_file="%cd%\%ext_name%.xpi" + +cd "%ext_name%" + +rem :: https://sevenzip.osdn.jp/chm/cmdline/index.htm +rem :: https://sevenzip.osdn.jp/chm/cmdline/commands/add.htm +7z a -tzip %xpi_file% -r . + +if not defined BUILD_ALL ( + echo. + pause +) diff --git a/dist/.bin/firefox/pack_xpi_with_zip.sh b/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh similarity index 67% rename from dist/.bin/firefox/pack_xpi_with_zip.sh rename to dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh index 4d0cf02e..834e476f 100755 --- a/dist/.bin/firefox/pack_xpi_with_zip.sh +++ b/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh @@ -2,13 +2,21 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +source "${DIR}/../../.env/constants.sh" + +if [ -z "$ext_name" ];then + echo 'script configuration is invalid:' + echo 'missing name of browser extension' + exit 1 +fi + # ------------------------------------------------------------------------------ # bootstrap function main { - cd "${DIR}/../.." + cd "${DIR}/../../.." cwd=$(pwd -P) - ext_name='PrivacyPass' + xpi_file="${cwd}/${ext_name}.xpi" cd "$ext_name" diff --git a/package.json b/package.json index 38b9a1d5..424685c6 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,13 @@ "license": "BSD-3-Clause", "type": "module", "scripts": { - "sjcl": "cd node_modules/sjcl && perl configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js", - "prebuild": "npm run sjcl", + "prebuild": "npm run clean && npm run sjcl", "build": "webpack", "pretest": "npm run sjcl", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "lint": "eslint ./src/**/*.{ts,tsx}", - "clean": "rimraf lib && rimraf dist/PrivacyPass && rimraf dist/PrivacyPass.crx* && rimraf dist/PrivacyPass.xpi" + "clean": "rimraf lib && rimraf dist/PrivacyPass && rimraf dist/PrivacyPass.crx* && rimraf dist/PrivacyPass.xpi", + "sjcl": "cd node_modules/sjcl && perl configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js" }, "dependencies": { "asn1-parser": "^1.1.8", From 2f3fad0730b21ac0fc4e502110dccf35d3813a54 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 20 Jan 2022 12:03:18 -0800 Subject: [PATCH 16/36] work in progress.. * the methodology used in this commit does NOT work - I fully expected that it would - it does NOT - this commit is being tagged so I can point to it later, if asked: "why didn't you just...?" * the methodology is to: - detect requests that solve catchas made to issuing domains * as was done previously - rather than make the issuing request then and there, wait until the corresponding call to "handleHeadersReceived" * problem: - there (apparently) is no corresponding call to "handleHeadersReceived" * to do: - add new hook for call to "chrome.webRequest.onCompleted" - add plumbing for all providers to receive this new hook function - ...hope it receives a corresponding call - - - - hCaptcha should send issuing requests from a different hook function similar to the way redemption works (but in reverse), detect solved captcha on issuing domains before requests are sent, but delay the processing of hits until after the response is received previously, a timeout was used to delay processing; but this methodology was inexact and could lead to failed attempts. note that this is in stark contrast to the Cloudflare provider, which cancels the requests that it detects, and processes those hits immediately without any delay. --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.test.ts | 242 +++++++++++------- src/background/providers/cloudflare.ts | 24 +- src/background/providers/hcaptcha.test.ts | 270 +++++++++++++------- src/background/providers/hcaptcha.ts | 46 ++-- 6 files changed, 370 insertions(+), 216 deletions(-) diff --git a/package.json b/package.json index 424685c6..5204d21e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.3.2", + "version": "3.4.0", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index ac7c619d..54d90952 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.3.2", + "version": "3.4.0", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index b925a295..9af1eafc 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -2,16 +2,6 @@ import { jest } from '@jest/globals'; import { CloudflareProvider } from './cloudflare'; import Token from '../token'; -beforeEach(() => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout'); -}); - -afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); -}); - export class StorageMock { store: Map; @@ -28,7 +18,7 @@ export class StorageMock { } } -test('getStoredTokens', () => { +test('setStoredTokens', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -36,13 +26,18 @@ test('getStoredTokens', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token()]; provider['setStoredTokens'](tokens); - const storedTokens = provider['getStoredTokens'](); - expect(storedTokens.map((token) => token.toString())).toEqual( - tokens.map((token) => token.toString()), - ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const storedTokens = JSON.parse(storage.store.get('tokens')!); + expect(storedTokens).toEqual(tokens.map((token) => token.toString())); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); -test('setStoredTokens', () => { +test('getStoredTokens', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -50,8 +45,17 @@ test('setStoredTokens', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token()]; provider['setStoredTokens'](tokens); - const storedTokens = JSON.parse(storage.store.get('tokens')!); - expect(storedTokens).toEqual(tokens.map((token) => token.toString())); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.map((token) => token.toString()), + ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('getBadgeText', () => { @@ -81,6 +85,22 @@ test('getBadgeText', () => { */ describe('issuance', () => { describe('handleBeforeRequest', () => { + const validDetails = { + method: 'POST', + url: 'https://captcha.website/?__cf_chl_captcha_tk__=query-param', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'xmlhttprequest' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestBody: { + formData: { + 'h-captcha-response': ['body-param'], + }, + }, + }; + test('valid request', async () => { const storage = new StorageMock(); const updateIcon = jest.fn(); @@ -92,33 +112,13 @@ describe('issuance', () => { return tokens; }); provider['issue'] = issue; - const url = 'https://captcha.website/?__cf_chl_captcha_tk__=query-param'; - const details = { - method: 'POST', - url, - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'xmlhttprequest' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestBody: { - formData: { - 'h-captcha-response': ['body-param'], - }, - }, - }; - const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: true }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + const result = provider.handleBeforeRequest(validDetails); + expect(result).toEqual({ cancel: true }); await Promise.resolve(); expect(issue.mock.calls.length).toBe(1); - expect(issue).toHaveBeenCalledWith(url, { + expect(issue).toHaveBeenCalledWith(validDetails.url, { 'h-captcha-response': 'body-param', }); @@ -145,7 +145,7 @@ describe('issuance', () => { * b. 'h-captcha-response' * c. 'cf_captcha_kind' */ - test('invalid request', async () => { + test('invalid request w/ no query param', async () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -153,21 +153,37 @@ describe('issuance', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const issue = jest.fn(async () => []); provider['issue'] = issue; + const details = { - method: 'GET', + ...validDetails, url: 'https://cloudflare.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'xmlhttprequest' as chrome.webRequest.ResourceType, - timeStamp: 1, + }; + const result = provider.handleBeforeRequest(details); + expect(result).toBeUndefined(); + await Promise.resolve(); + + expect(issue).not.toHaveBeenCalled(); + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); + }); + + test('invalid request w/ no body param', async () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); + const issue = jest.fn(async () => []); + provider['issue'] = issue; + + const details = { + ...validDetails, requestBody: {}, }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); + await Promise.resolve(); - expect(setTimeout).not.toHaveBeenCalled(); expect(issue).not.toHaveBeenCalled(); expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); @@ -200,6 +216,7 @@ describe('issuance', () => { describe('redemption', () => { describe('handleHeadersReceived', () => { const validDetails = { + method: 'GET', url: 'https://cloudflare.com/', requestId: 'xxx', frameId: 1, @@ -207,7 +224,6 @@ describe('redemption', () => { tabId: 1, type: 'main_frame' as chrome.webRequest.ResourceType, timeStamp: 1, - statusLine: 'HTTP/1.1 403 Forbidden', statusCode: 403, responseHeaders: [ @@ -216,7 +232,6 @@ describe('redemption', () => { value: '1', }, ], - method: 'GET', }; test('valid response with tokens', () => { @@ -227,18 +242,30 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toEqual({ redirectUrl: details.url }); + // Expect redeemInfo to be set. const redeemInfo = provider['redeemInfo']; expect(redeemInfo!.requestId).toEqual(details.requestId); expect(redeemInfo!.token.toString()).toEqual(tokens[0].toString()); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(updateIcon).toHaveBeenLastCalledWith((tokens.length - 1).toString()); + // Expect a token is used. const storedTokens = provider['getStoredTokens'](); expect(storedTokens.map((token) => token.toString())).toEqual( tokens.slice(1).map((token) => token.toString()), ); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('valid response without tokens', () => { @@ -248,12 +275,20 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); provider['setStoredTokens']([]); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith('0'); + const details = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(updateIcon).toHaveBeenLastCalledWith('0'); + expect(navigateUrl).not.toHaveBeenCalled(); }); - test('captcha.website response', () => { + test('no response from an issuing domain', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -261,10 +296,19 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); - const details = validDetails; - details.url = 'https://captcha.website/'; + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const details = { + ...validDetails, + url: 'https://captcha.website/', + }; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); /* @@ -272,7 +316,7 @@ describe('redemption', () => { * 1. The status code is not 403. * 2. There is no HTTP header of "cf-chl-bypass: 1" */ - test('invalid response', () => { + test('invalid response w/ wrong status code', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -280,25 +324,59 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = { - url: 'https://cloudflare.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - - statusLine: 'HTTP/1.1 403 Forbidden', - statusCode: 403, - method: 'GET', + ...validDetails, + statusLine: 'HTTP/1.1 200 OK', + statusCode: 200, + }; + const result = provider.handleHeadersReceived(details); + expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); + }); + + test('invalid response w/ no bypass header', () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const details = { + ...validDetails, + responseHeaders: [], }; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); }); describe('handleBeforeSendHeaders', () => { + const validDetails = { + method: 'GET', + url: 'https://cloudflare.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'main_frame' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestHeaders: [], + }; + test('with redeemInfo', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); @@ -314,17 +392,8 @@ describe('redemption', () => { token, }; provider['redeemInfo'] = redeemInfo; - const details = { - method: 'GET', - url: 'https://cloudflare.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestHeaders: [], - }; + + const details = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toEqual({ requestHeaders: [ @@ -334,9 +403,12 @@ describe('redemption', () => { }, ], }); + const newRedeemInfo = provider['redeemInfo']; expect(newRedeemInfo).toBeNull(); + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('without redeemInfo', () => { @@ -346,20 +418,12 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); - const details = { - method: 'GET', - url: 'https://cloudflare.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestHeaders: [], - }; + const details = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toBeUndefined(); + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); }); }); }); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 0780f9ca..abc87809 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -295,20 +295,16 @@ export class CloudflareProvider extends Provider { } } - // delay the request to issue tokens until next tick of the event loop - setTimeout( - async () => { - // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); - - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); - - this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); - }, - 0 - ); + (async () => { + // Issue tokens. + const tokens = await this.issue(details.url, flattenFormData); + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + + this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + })(); // safe to cancel return { cancel: true }; diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index 089058c1..32672605 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -2,16 +2,6 @@ import { jest } from '@jest/globals'; import { HcaptchaProvider } from './hcaptcha'; import Token from '../token'; -beforeEach(() => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout'); -}); - -afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); -}); - export class StorageMock { store: Map; @@ -28,7 +18,7 @@ export class StorageMock { } } -test('getStoredTokens', () => { +test('setStoredTokens', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -36,13 +26,18 @@ test('getStoredTokens', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token()]; provider['setStoredTokens'](tokens); - const storedTokens = provider['getStoredTokens'](); - expect(storedTokens.map((token) => token.toString())).toEqual( - tokens.map((token) => token.toString()), - ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const storedTokens = JSON.parse(storage.store.get('tokens')!); + expect(storedTokens).toEqual(tokens.map((token) => token.toString())); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); -test('setStoredTokens', () => { +test('getStoredTokens', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -50,8 +45,17 @@ test('setStoredTokens', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token()]; provider['setStoredTokens'](tokens); - const storedTokens = JSON.parse(storage.store.get('tokens')!); - expect(storedTokens).toEqual(tokens.map((token) => token.toString())); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.map((token) => token.toString()), + ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('getBadgeText', () => { @@ -67,56 +71,79 @@ test('getBadgeText', () => { }); /* - * The issuance involves handleBeforeRequest listener. + * The issuance involves handleBeforeRequest and handleHeadersReceived + * listeners. In handleBeforeRequest listener, * 1. Firstly, the listener check if the request looks like the one that we * should send an issuance request. * 2. If it passes the check, the listener returns the cancel command to * explicitly prevent cancelling the request. * If not, it returns nothing and let the request continue. - * 3. At the same time the listener returns, it calls a private method - * "issue" to send an issuance request to the server and the method return + * 3. The listener sets "issueInfo" property which includes the request id + * and other request details. The property will be used by + * handleHeadersReceived to issue new tokens. + * + * In handleHeadersReceived, + * 1. The listener will check if the provided request id matches the + * request id in "issueInfo". If so, it means that the response is to the + * request checked by handleBeforeRequest that should trigger an issuance request. + * 2. If it passes the check, the listener calls a private method + * "issue" to send an issuance request to the server and the method returns * an array of issued tokens. - * 4. The listener stored the issued tokens in the storage. - * 5. The listener reloads the tab to get the proper web page for the tab.); + * 3. The listener stores the issued tokens in the storage. + * 4. The listener reloads the tab to get the proper web page for the tab. */ describe('issuance', () => { describe('handleBeforeRequest', () => { + const validDetails = { + method: 'POST', + url: 'https://hcaptcha.com/checkcaptcha/xxx?s=00000000-0000-0000-0000-000000000000', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'xmlhttprequest' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestBody: { + formData: {}, + }, + }; + test('valid request', async () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + let result, issueInfo + + const reqDetails = validDetails; + result = provider.handleBeforeRequest(reqDetails); + expect(result).toEqual({ cancel: false }); + + // Expect issueInfo to be set. + issueInfo = provider['issueInfo']; + expect(issueInfo!.requestId).toEqual(reqDetails.requestId); + expect(issueInfo!.url).toEqual(reqDetails.url); + expect(issueInfo!.formData).toEqual({}); + const tokens = [new Token(), new Token(), new Token()]; const issue = jest.fn(async () => { return tokens; }); provider['issue'] = issue; - const url = 'https://www.hcaptcha.com/checkcaptcha/?s=00000000-0000-0000-0000-000000000000'; - const details = { - method: 'POST', - url, - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'xmlhttprequest' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestBody: { - formData: {}, - }, - }; - const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + const resDetails = { + ...validDetails, + statusLine: 'HTTP/1.1 200 OK', + statusCode: 200, + responseHeaders: [], + }; + result = provider.handleHeadersReceived(resDetails); + expect(result).toBeUndefined(); await Promise.resolve(); expect(issue.mock.calls.length).toBe(1); - expect(issue).toHaveBeenCalledWith(url, {}); + expect(issue).toHaveBeenCalledWith(issueInfo!.url, issueInfo!.formData); // Expect the tokens are added. const storedTokens = provider['getStoredTokens'](); @@ -124,6 +151,10 @@ describe('issuance', () => { tokens.map((token) => token.toString()), ); + // Expect issueInfo to be null. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); @@ -134,37 +165,50 @@ describe('issuance', () => { /* * The request is invalid if any of the followings is true: * 1. It has no url param of any of the followings: - * a. '__cf_chl_captcha_tk__' - * b. '__cf_chl_managed_tk__' - * 2. It has no body param of any of the followings: - * a. 'g-recaptcha-response' - * b. 'h-captcha-response' - * c. 'cf_captcha_kind' + * a. 's=00000000-0000-0000-0000-000000000000' + * 2. Its pathname does not contain of any of the followings: + * a. '/checkcaptcha' */ - test('invalid request', async () => { + test('invalid request w/ no query param', async () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - const issue = jest.fn(async () => []); - provider['issue'] = issue; + const details = { - method: 'GET', - url: 'https://www.hcaptcha.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'xmlhttprequest' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestBody: {}, + ...validDetails, + url: 'https://hcaptcha.com/checkcaptcha/xxx?s=', + }; + const result = provider.handleBeforeRequest(details); + expect(result).toBeUndefined(); + + // Expect issueInfo to be null. + const issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); + }); + + test('invalid request w/ no matching pathname', async () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); + + const details = { + ...validDetails, + url: 'https://hcaptcha.com/getcaptcha?s=00000000-0000-0000-0000-000000000000', }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); - expect(setTimeout).not.toHaveBeenCalled(); - expect(issue).not.toHaveBeenCalled(); + // Expect issueInfo to be null. + const issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); }); @@ -196,6 +240,7 @@ describe('issuance', () => { describe('redemption', () => { describe('handleHeadersReceived', () => { const validDetails = { + method: 'GET', url: 'https://non-issuing-domain.example.com/', requestId: 'xxx', frameId: 1, @@ -203,7 +248,6 @@ describe('redemption', () => { tabId: 1, type: 'main_frame' as chrome.webRequest.ResourceType, timeStamp: 1, - statusLine: 'HTTP/1.1 403 Forbidden', statusCode: 403, responseHeaders: [ @@ -212,7 +256,6 @@ describe('redemption', () => { value: '2', }, ], - method: 'GET', }; test('valid response with tokens', () => { @@ -223,18 +266,30 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toEqual({ redirectUrl: details.url }); + // Expect redeemInfo to be set. const redeemInfo = provider['redeemInfo']; expect(redeemInfo!.requestId).toEqual(details.requestId); expect(redeemInfo!.token.toString()).toEqual(tokens[0].toString()); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(updateIcon).toHaveBeenLastCalledWith((tokens.length - 1).toString()); + // Expect a token is used. const storedTokens = provider['getStoredTokens'](); expect(storedTokens.map((token) => token.toString())).toEqual( tokens.slice(1).map((token) => token.toString()), ); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('valid response without tokens', () => { @@ -244,9 +299,17 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); provider['setStoredTokens']([]); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith('0'); + const details = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(2); + expect(updateIcon).toHaveBeenLastCalledWith('0'); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('no response from an issuing domain', () => { @@ -257,20 +320,27 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = { ...validDetails, - url: 'https://www.hcaptcha.com/privacy-pass' + url: 'https://www.hcaptcha.com/privacy-pass', }; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); /* * The response is invalid if any of the followings is true: * 1. The status code is not 403. - * 2. There is no HTTP header of "cf-chl-bypass: 2" + * 2. There is no HTTP header of "cf-chl-bypass: 1" */ - test('no response when the status code is not 403', () => { + test('invalid response w/ wrong status code', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -278,16 +348,23 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = { ...validDetails, statusLine: 'HTTP/1.1 200 OK', - statusCode: 200 + statusCode: 200, }; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); - test('no response when there is no HTTP header of "cf-chl-bypass: 2"', () => { + test('invalid response w/ no bypass header', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -295,16 +372,35 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); const tokens = [new Token(), new Token(), new Token()]; provider['setStoredTokens'](tokens); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + const details = { ...validDetails, - responseHeaders: undefined + responseHeaders: [], }; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(navigateUrl).not.toHaveBeenCalled(); }); }); describe('handleBeforeSendHeaders', () => { + const validDetails = { + method: 'GET', + url: 'https://www.hcaptcha.com/', + requestId: 'xxx', + frameId: 1, + parentFrameId: 1, + tabId: 1, + type: 'main_frame' as chrome.webRequest.ResourceType, + timeStamp: 1, + requestHeaders: [], + }; + test('with redeemInfo', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); @@ -320,17 +416,8 @@ describe('redemption', () => { token, }; provider['redeemInfo'] = redeemInfo; - const details = { - method: 'GET', - url: 'https://www.hcaptcha.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestHeaders: [], - }; + + const details = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toEqual({ requestHeaders: [ @@ -340,9 +427,12 @@ describe('redemption', () => { }, ], }); + const newRedeemInfo = provider['redeemInfo']; expect(newRedeemInfo).toBeNull(); + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); }); test('without redeemInfo', () => { @@ -352,20 +442,12 @@ describe('redemption', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - const details = { - method: 'GET', - url: 'https://www.hcaptcha.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestHeaders: [], - }; + const details = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toBeUndefined(); + expect(updateIcon).not.toHaveBeenCalled(); + expect(navigateUrl).not.toHaveBeenCalled(); }); }); }); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 8cec2413..2ebae778 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -40,6 +40,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4OifvSTxGcy3T/yac6LVugArFb89 wvqGivp0/54wgeyWkvUZiUdlbIQF7BuGeO9C4sx4nHkpAgRfvd8jdBGz9g== -----END PUBLIC KEY-----`; +interface IssueInfo { + requestId: string; + url: string; + formData: { [key: string]: string[] | string }; +} + interface RedeemInfo { requestId: string; token: Token; @@ -57,6 +63,7 @@ export class HcaptchaProvider extends Provider { private VOPRF: voprf.VOPRF; private callbacks: Callbacks; private storage: Storage; + private issueInfo: IssueInfo | null; private redeemInfo: RedeemInfo | null; constructor(storage: Storage, callbacks: Callbacks) { @@ -65,6 +72,7 @@ export class HcaptchaProvider extends Provider { this.VOPRF = new voprf.VOPRF(voprf.defaultECSettings); this.callbacks = callbacks; this.storage = storage; + this.issueInfo = null; this.redeemInfo = null; } @@ -286,38 +294,42 @@ export class HcaptchaProvider extends Provider { return; } - const flattenFormData: { [key: string]: string[] | string } = {}; + this.issueInfo = { requestId: details.requestId, url: details.url, formData: {} }; + for (const key in formData) { if (Array.isArray(formData[key]) && (formData[key].length === 1)) { const [value] = formData[key]; - flattenFormData[key] = value; + this.issueInfo.formData[key] = value; } else { - flattenFormData[key] = formData[key]; + this.issueInfo.formData[key] = formData[key]; } } - // delay the request to issue tokens until next tick of the event loop - setTimeout( - async () => { + // do NOT cancel the original captcha solve request + return { cancel: false }; + } + + handleHeadersReceived( + details: chrome.webRequest.WebResponseHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + // Check if it's the response of the request that solved a captcha on a domain that issues tokens. + if (this.issueInfo !== null && details.requestId === this.issueInfo.requestId) { + (async () => { // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); + const tokens = await this.issue(this.issueInfo!.url, this.issueInfo!.formData); + + // Clear the issue info to indicate that we are already issuing the tokens. + this.issueInfo = null; // Store tokens. const cached = this.getStoredTokens(); this.setStoredTokens(cached.concat(tokens)); this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); - }, - 0 - ); - - // do NOT cancel the original captcha solve request - return { cancel: false }; - } + })(); + return; + } - handleHeadersReceived( - details: chrome.webRequest.WebResponseHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { // Don't redeem a token on the issuing website. if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { return; From e101fc7a5f2d2ff3e820c773a8e2eec4324f97bf Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 20 Jan 2022 13:53:26 -0800 Subject: [PATCH 17/36] hCaptcha sends issuing requests from 'onCompleted' hook function similar to the way redemption works (but in reverse), detect solved captcha on issuing domains before requests are sent, but delay the processing of hits until after the response is received previously, a timeout was used to delay processing; but this methodology was inexact and could lead to failed attempts. note that this is in stark contrast to the Cloudflare provider, which cancels the requests that it detects, and processes those hits immediately without any delay. --- src/background/index.ts | 21 ++++--- .../listeners/webRequestListener.ts | 62 ++++++++++++------- src/background/providers/cloudflare.test.ts | 2 +- src/background/providers/cloudflare.ts | 12 ++++ src/background/providers/hcaptcha.test.ts | 12 ++-- src/background/providers/hcaptcha.ts | 51 +++++++++------ src/background/providers/provider.ts | 6 ++ src/background/tab.ts | 16 +++++ 8 files changed, 129 insertions(+), 53 deletions(-) diff --git a/src/background/index.ts b/src/background/index.ts index 8e5c0630..780d0657 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -11,6 +11,8 @@ import { handleBeforeRequest, handleBeforeSendHeaders, handleHeadersReceived, + handleOnCompleted, + handleOnErrorOccurred, } from './listeners/webRequestListener'; import { @@ -71,21 +73,26 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs: chrome. }); chrome.webRequest.onBeforeRequest.addListener(handleBeforeRequest, { urls: [''] }, [ - 'requestBody', 'blocking', + 'requestBody', ]); -chrome.webRequest.onBeforeSendHeaders.addListener( - handleBeforeSendHeaders, - { urls: [''] }, - ['requestHeaders', 'blocking'], -); +chrome.webRequest.onBeforeSendHeaders.addListener(handleBeforeSendHeaders, { urls: [''] }, [ + 'blocking', + 'requestHeaders', +]); chrome.webRequest.onHeadersReceived.addListener(handleHeadersReceived, { urls: [''] }, [ - 'responseHeaders', 'blocking', + 'responseHeaders', ]); +chrome.webRequest.onCompleted.addListener(handleOnCompleted, { urls: [''] }, [ + 'responseHeaders', +]); + +chrome.webRequest.onErrorOccurred.addListener(handleOnErrorOccurred, { urls: [''] }); + chrome.cookies.onChanged.addListener(handleChangedCookies); chrome.runtime.onMessage.addListener(handleReceivedMessage); diff --git a/src/background/listeners/webRequestListener.ts b/src/background/listeners/webRequestListener.ts index 02e9d604..7ac19c2d 100644 --- a/src/background/listeners/webRequestListener.ts +++ b/src/background/listeners/webRequestListener.ts @@ -1,38 +1,58 @@ -export function handleBeforeRequest( - details: chrome.webRequest.WebRequestBodyDetails, -): chrome.webRequest.BlockingResponse | void { - if (details.tabId === chrome.tabs.TAB_ID_NONE) { +import { Tab } from '../tab'; + +function getTab(id: number): Tab | null { + if (id === chrome.tabs.TAB_ID_NONE) { // The request does not correspond to any tab. - return; + return null; } - const tab = window.TABS.get(details.tabId); // The tab can be removed already if the request comes after the tab is closed. - return tab?.handleBeforeRequest(details); + const tab: Tab | void = window.TABS.get(id); + + return (tab === undefined) ? null : tab; +} + +export function handleBeforeRequest( + details: chrome.webRequest.WebRequestBodyDetails, +): chrome.webRequest.BlockingResponse | void { + const tab = getTab(details.tabId); + if (tab === null) return; + + return tab!.handleBeforeRequest(details); } export function handleBeforeSendHeaders( details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - if (details.tabId === chrome.tabs.TAB_ID_NONE) { - // The request does not correspond to any tab. - return; - } + const tab = getTab(details.tabId); + if (tab === null) return; - const tab = window.TABS.get(details.tabId); - // The tab can be removed already if the request comes after the tab is closed. - return tab?.handleBeforeSendHeaders(details); + return tab!.handleBeforeSendHeaders(details); } export function handleHeadersReceived( details: chrome.webRequest.WebResponseHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - if (details.tabId === chrome.tabs.TAB_ID_NONE) { - // The request does not correspond to any tab. - return; - } + const tab = getTab(details.tabId); + if (tab === null) return; + + return tab!.handleHeadersReceived(details); +} + +export function handleOnCompleted( + details: chrome.webRequest.WebResponseHeadersDetails, +): void { + const tab = getTab(details.tabId); + if (tab === null) return; + + return tab!.handleOnCompleted(details); +} + +export function handleOnErrorOccurred( + details: chrome.webRequest.WebResponseErrorDetails, +): void { + const tab = getTab(details.tabId); + if (tab === null) return; - const tab = window.TABS.get(details.tabId); - // The tab can be removed already if the response comes after the tab is closed. - return tab?.handleHeadersReceived(details); + return tab!.handleOnErrorOccurred(details); } diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 9af1eafc..e6806855 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -156,7 +156,7 @@ describe('issuance', () => { const details = { ...validDetails, - url: 'https://cloudflare.com/', + url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index abc87809..f284fe8f 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -350,4 +350,16 @@ export class CloudflareProvider extends Provider { redirectUrl: details.url, }; } + + handleOnCompleted( + _details: chrome.webRequest.WebResponseHeadersDetails, + ): void { + return; + } + + handleOnErrorOccurred( + _details: chrome.webRequest.WebResponseErrorDetails, + ): void { + return; + } } diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index 32672605..2c02743e 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -71,7 +71,7 @@ test('getBadgeText', () => { }); /* - * The issuance involves handleBeforeRequest and handleHeadersReceived + * The issuance involves handleBeforeRequest and handleOnCompleted * listeners. In handleBeforeRequest listener, * 1. Firstly, the listener check if the request looks like the one that we * should send an issuance request. @@ -80,9 +80,9 @@ test('getBadgeText', () => { * If not, it returns nothing and let the request continue. * 3. The listener sets "issueInfo" property which includes the request id * and other request details. The property will be used by - * handleHeadersReceived to issue new tokens. + * handleOnCompleted to issue new tokens. * - * In handleHeadersReceived, + * In handleOnCompleted, * 1. The listener will check if the provided request id matches the * request id in "issueInfo". If so, it means that the response is to the * request checked by handleBeforeRequest that should trigger an issuance request. @@ -138,7 +138,7 @@ describe('issuance', () => { statusCode: 200, responseHeaders: [], }; - result = provider.handleHeadersReceived(resDetails); + result = provider.handleOnCompleted(resDetails); expect(result).toBeUndefined(); await Promise.resolve(); @@ -178,7 +178,7 @@ describe('issuance', () => { const details = { ...validDetails, - url: 'https://hcaptcha.com/checkcaptcha/xxx?s=', + url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); @@ -200,7 +200,7 @@ describe('issuance', () => { const details = { ...validDetails, - url: 'https://hcaptcha.com/getcaptcha?s=00000000-0000-0000-0000-000000000000', + url: validDetails.url.replace(/checkcaptcha/g, 'getcaptcha'), }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 2ebae778..a5819b2e 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -312,24 +312,6 @@ export class HcaptchaProvider extends Provider { handleHeadersReceived( details: chrome.webRequest.WebResponseHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - // Check if it's the response of the request that solved a captcha on a domain that issues tokens. - if (this.issueInfo !== null && details.requestId === this.issueInfo.requestId) { - (async () => { - // Issue tokens. - const tokens = await this.issue(this.issueInfo!.url, this.issueInfo!.formData); - - // Clear the issue info to indicate that we are already issuing the tokens. - this.issueInfo = null; - - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); - - this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); - })(); - return; - } - // Don't redeem a token on the issuing website. if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { return; @@ -367,4 +349,37 @@ export class HcaptchaProvider extends Provider { redirectUrl: details.url, }; } + + private sendIssueRequest(requestId: string): void { + // Check if it's the response of the request that solved a captcha on a domain that issues tokens. + if (this.issueInfo !== null && requestId === this.issueInfo.requestId) { + this.issueInfo.requestId = 'issued'; + + (async () => { + // Issue tokens. + const tokens = await this.issue(this.issueInfo!.url, this.issueInfo!.formData); + + // Clear the issue info to indicate that we are already issuing the tokens. + this.issueInfo = null; + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + + this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); + })(); + } + } + + handleOnCompleted( + details: chrome.webRequest.WebResponseHeadersDetails, + ): void { + this.sendIssueRequest(details.requestId); + } + + handleOnErrorOccurred( + details: chrome.webRequest.WebResponseErrorDetails, + ): void { + this.sendIssueRequest(details.requestId); + } } diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts index 20970503..b7d6bd95 100644 --- a/src/background/providers/provider.ts +++ b/src/background/providers/provider.ts @@ -30,6 +30,12 @@ export abstract class Provider { abstract handleBeforeSendHeaders( details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void; + abstract handleOnCompleted( + details: chrome.webRequest.WebResponseHeadersDetails, + ): void; + abstract handleOnErrorOccurred( + details: chrome.webRequest.WebResponseErrorDetails, + ): void; } // ----------------------------------------------------------------------------- diff --git a/src/background/tab.ts b/src/background/tab.ts index 9e053589..33f3a461 100644 --- a/src/background/tab.ts +++ b/src/background/tab.ts @@ -133,4 +133,20 @@ export class Tab { return result; } + + handleOnCompleted( + details: chrome.webRequest.WebResponseHeadersDetails, + ): void { + if (this.context !== null) { + this.context.handleOnCompleted(details); + } + } + + handleOnErrorOccurred( + details: chrome.webRequest.WebResponseErrorDetails, + ): void { + if (this.context !== null) { + this.context.handleOnErrorOccurred(details); + } + } } From 615f1899c1232708809b9aa12ed38bc1ddefae2c Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 20 Jan 2022 18:33:45 -0800 Subject: [PATCH 18/36] minor css tweak to popup window * each row of providers contains: name, token count * on very old browsers running the CRX2 extension, the count of tokens wasn't correctly horizontally aligned * now it is - the CRX2 extension on Chrome 30 now looks identical to the CRX3 extension on Chrome 90 ..and all versions inbetween ..and more --- package.json | 2 +- public/manifest.json | 2 +- src/popup/components/PassButton/styles.module.scss | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5204d21e..227a1a1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.4.0", + "version": "3.4.1", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 54d90952..b51c8916 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.4.0", + "version": "3.4.1", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/popup/components/PassButton/styles.module.scss b/src/popup/components/PassButton/styles.module.scss index 58a4f574..0b6f2b67 100644 --- a/src/popup/components/PassButton/styles.module.scss +++ b/src/popup/components/PassButton/styles.module.scss @@ -10,10 +10,15 @@ $_font-weight: 200; background-color: colors.$light-grey; font-size: $_font-size; font-weight: $_font-weight; + + white-space: nowrap; + line-height: 1em; + height: 1em; + clear: both; } .content { - display: inline; + float: left; } .value { From 37a5271f50c3e13ffa3c79e6b53d4039bdc636c0 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 22 Jan 2022 13:21:37 -0800 Subject: [PATCH 19/36] minor updates to the 'dist/.bin' build scripts --- dist/.bin/build_all.sh | 26 +++++++++++++++++-- .../chromium/.common/pack_crx_with_chrome.bat | 8 +++--- .../chromium/.common/pack_crx_with_chrome.sh | 8 +++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/dist/.bin/build_all.sh b/dist/.bin/build_all.sh index 0b0602a8..9f49190d 100755 --- a/dist/.bin/build_all.sh +++ b/dist/.bin/build_all.sh @@ -1,10 +1,32 @@ #!/usr/bin/env bash +# ------------------------------------------------------------------------------ +# configuration + +# 'USE_OPENSSL' determines how CRX2 and CRX3 extensions are built +# - any non-empty value will use: +# OpenSSL +# - an empty value will use: +# 2x different versions of Chrome +# 1x older for CRX2: < 64.0.3242.0 +# 1x newer for CRX3: >= 64.0.3242.0 +USE_OPENSSL='1' + +# ------------------------------------------------------------------------------ + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" "${DIR}/build/build.sh" -"${DIR}/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh" +if [ -n "$USE_OPENSSL" ];then + "${DIR}/pack extensions/chromium/crx3/pack_crx3_with_openssl.sh" +else + "${DIR}/pack extensions/chromium/crx3/pack_crx3_with_chrome.sh" +fi "${DIR}/pack extensions/firefox/pack_xpi_with_zip.sh" "${DIR}/inject ES6 polyfills/inject_es6_polyfills.sh" -"${DIR}/pack extensions/chromium/crx2/pack_crx2_with_openssl.sh" +if [ -n "$USE_OPENSSL" ];then + "${DIR}/pack extensions/chromium/crx2/pack_crx2_with_openssl.sh" +else + "${DIR}/pack extensions/chromium/crx2/pack_crx2_with_chrome.sh" +fi diff --git a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat index 4f73e016..f7ac6759 100755 --- a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat +++ b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat @@ -8,11 +8,11 @@ if not defined ext_name ( cd /D "%~dp0..\..\..\.." -set ext_dir="%cd%\PrivacyPass" -set ext_key="%cd%\PrivacyPass.pem" +set ext_dir="%cd%\%ext_name%" +set ext_key="%cd%\%ext_name%.pem" if exist %ext_key% ( - chrome --pack-extension=%ext_dir% --pack-extension-key=%ext_key% + chrome --disable-gpu --disable-software-rasterizer --pack-extension=%ext_dir% --pack-extension-key=%ext_key% ) else ( - chrome --pack-extension=%ext_dir% + chrome --disable-gpu --disable-software-rasterizer --pack-extension=%ext_dir% ) diff --git a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh index d78b8c9c..8e833bb7 100755 --- a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh +++ b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh @@ -14,13 +14,13 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function main { cd "${DIR}/../../../.." cwd=$(realpath .) - ext_dir="${cwd}/PrivacyPass" - ext_key="${cwd}/PrivacyPass.pem" + ext_dir="${cwd}/${ext_name}" + ext_key="${cwd}/${ext_name}.pem" if [ -f "$ext_key" ];then - chrome "--pack-extension=${ext_dir}" "--pack-extension-key=${ext_key}" + chrome --disable-gpu --disable-software-rasterizer "--pack-extension=${ext_dir}" "--pack-extension-key=${ext_key}" else - chrome "--pack-extension=${ext_dir}" + chrome --disable-gpu --disable-software-rasterizer "--pack-extension=${ext_dir}" fi } From 706a9e7f47e9bca6e88c1b249c721b1a18aa0e11 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 26 Jan 2022 00:13:48 -0800 Subject: [PATCH 20/36] minor updates to the 'dist/.bin' build scripts --- .gitignore | 5 +++-- dist/.bin/.env/build_development.bat | 3 +++ dist/.bin/.env/build_development.sh | 3 +++ dist/.bin/.env/build_production.bat | 3 +++ dist/.bin/.env/build_production.sh | 3 +++ dist/.bin/build/build_development.bat | 4 ++++ dist/.bin/build/build_development.sh | 6 ++++++ dist/.bin/build/build_production.bat | 4 ++++ dist/.bin/build/build_production.sh | 6 ++++++ dist/.bin/build_all_development.bat | 4 ++++ dist/.bin/build_all_development.sh | 6 ++++++ dist/.bin/build_all_production.bat | 4 ++++ dist/.bin/build_all_production.sh | 6 ++++++ webpack.config.js | 2 +- 14 files changed, 56 insertions(+), 3 deletions(-) create mode 100755 dist/.bin/.env/build_development.bat create mode 100755 dist/.bin/.env/build_development.sh create mode 100755 dist/.bin/.env/build_production.bat create mode 100755 dist/.bin/.env/build_production.sh create mode 100755 dist/.bin/build/build_development.bat create mode 100755 dist/.bin/build/build_development.sh create mode 100755 dist/.bin/build/build_production.bat create mode 100755 dist/.bin/build/build_production.sh create mode 100755 dist/.bin/build_all_development.bat create mode 100755 dist/.bin/build_all_development.sh create mode 100755 dist/.bin/build_all_production.bat create mode 100755 dist/.bin/build_all_production.sh diff --git a/.gitignore b/.gitignore index a819948c..2b066c91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -/node_modules -/lib +/node_modules/ +/lib/ +/dist/lib/ /dist/PrivacyPass/ /dist/PrivacyPass.pem /dist/PrivacyPass.crx* diff --git a/dist/.bin/.env/build_development.bat b/dist/.bin/.env/build_development.bat new file mode 100755 index 00000000..c75358d2 --- /dev/null +++ b/dist/.bin/.env/build_development.bat @@ -0,0 +1,3 @@ +@echo off + +set NODE_ENV=development diff --git a/dist/.bin/.env/build_development.sh b/dist/.bin/.env/build_development.sh new file mode 100755 index 00000000..04626cff --- /dev/null +++ b/dist/.bin/.env/build_development.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +export NODE_ENV='development' diff --git a/dist/.bin/.env/build_production.bat b/dist/.bin/.env/build_production.bat new file mode 100755 index 00000000..e36209d5 --- /dev/null +++ b/dist/.bin/.env/build_production.bat @@ -0,0 +1,3 @@ +@echo off + +set NODE_ENV=production diff --git a/dist/.bin/.env/build_production.sh b/dist/.bin/.env/build_production.sh new file mode 100755 index 00000000..a3f80413 --- /dev/null +++ b/dist/.bin/.env/build_production.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +export NODE_ENV='production' diff --git a/dist/.bin/build/build_development.bat b/dist/.bin/build/build_development.bat new file mode 100755 index 00000000..04d8b1ee --- /dev/null +++ b/dist/.bin/build/build_development.bat @@ -0,0 +1,4 @@ +@echo off + +call "%~dp0..\.env\%~nx0" +call "%~dp0.\build.bat" diff --git a/dist/.bin/build/build_development.sh b/dist/.bin/build/build_development.sh new file mode 100755 index 00000000..5e442594 --- /dev/null +++ b/dist/.bin/build/build_development.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/../.env/build_development.sh" +source "${DIR}/build.sh" diff --git a/dist/.bin/build/build_production.bat b/dist/.bin/build/build_production.bat new file mode 100755 index 00000000..04d8b1ee --- /dev/null +++ b/dist/.bin/build/build_production.bat @@ -0,0 +1,4 @@ +@echo off + +call "%~dp0..\.env\%~nx0" +call "%~dp0.\build.bat" diff --git a/dist/.bin/build/build_production.sh b/dist/.bin/build/build_production.sh new file mode 100755 index 00000000..8f43b00c --- /dev/null +++ b/dist/.bin/build/build_production.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/../.env/build_production.sh" +source "${DIR}/build.sh" diff --git a/dist/.bin/build_all_development.bat b/dist/.bin/build_all_development.bat new file mode 100755 index 00000000..a41ac5dc --- /dev/null +++ b/dist/.bin/build_all_development.bat @@ -0,0 +1,4 @@ +@echo off + +call "%~dp0.\.env\build_development.bat" +call "%~dp0.\build_all.bat" diff --git a/dist/.bin/build_all_development.sh b/dist/.bin/build_all_development.sh new file mode 100755 index 00000000..f6d9b138 --- /dev/null +++ b/dist/.bin/build_all_development.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/.env/build_development.sh" +source "${DIR}/build_all.sh" diff --git a/dist/.bin/build_all_production.bat b/dist/.bin/build_all_production.bat new file mode 100755 index 00000000..d710e869 --- /dev/null +++ b/dist/.bin/build_all_production.bat @@ -0,0 +1,4 @@ +@echo off + +call "%~dp0.\.env\build_production.bat" +call "%~dp0.\build_all.bat" diff --git a/dist/.bin/build_all_production.sh b/dist/.bin/build_all_production.sh new file mode 100755 index 00000000..4197342b --- /dev/null +++ b/dist/.bin/build_all_production.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/.env/build_production.sh" +source "${DIR}/build_all.sh" diff --git a/webpack.config.js b/webpack.config.js index 87e7c089..d46ee954 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ const common = { mode: 'production', target: ['web', 'es5'], optimization: { - minimize: true, + minimize: (process.env.NODE_ENV === 'production'), }, }; From baa5e809c39f30b304933b4751baa39776e9e02e Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 26 Jan 2022 00:23:23 -0800 Subject: [PATCH 21/36] add popup menu buttons to backup and restore tokens in local storage --- package.json | 2 +- public/_locales/en/messages.json | 12 +++ public/manifest.json | 2 +- src/background/listeners/messageListener.ts | 108 +++++++++++++++---- src/popup/components/App/index.tsx | 14 ++- src/popup/components/BackupButton/index.tsx | 13 +++ src/popup/components/RestoreButton/index.tsx | 13 +++ src/popup/index.tsx | 9 +- src/popup/store.ts | 99 ++++++++++++++++- 9 files changed, 237 insertions(+), 35 deletions(-) create mode 100644 src/popup/components/BackupButton/index.tsx create mode 100644 src/popup/components/RestoreButton/index.tsx diff --git a/package.json b/package.json index 227a1a1a..852cf6f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.4.1", + "version": "3.5.0", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index bed1f1de..60a7a4fd 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -7,6 +7,10 @@ "message": "Client support for Privacy Pass anonymous authorization protocol.", "description": "description of extension included by 'manifest.json'." }, + "labelFileBackup": { + "message": "backup", + "description": "label included in default filename of JSON backups generated by '@popup/store'." + }, "labelAppVersion": { "message": "Version", "description": "label of current extension version displayed by '@popup/components/Header'." @@ -23,6 +27,14 @@ "message": "Get more passes!", "description": "mouseover text displayed by '@popup/components/PassButton'." }, + "ctaBackupAllPasses": { + "message": "Backup All Passes", + "description": "displayed by '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restore Passes From Backup", + "description": "displayed by '@popup/components/RestoreButton'." + }, "ctaClearAllPasses": { "message": "Clear All Passes", "description": "displayed by '@popup/components/ClearButton'." diff --git a/public/manifest.json b/public/manifest.json index b51c8916..20d7b09e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.4.1", + "version": "3.5.0", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/listeners/messageListener.ts b/src/background/listeners/messageListener.ts index b22f6543..4aca5077 100644 --- a/src/background/listeners/messageListener.ts +++ b/src/background/listeners/messageListener.ts @@ -2,41 +2,111 @@ import { forceUpdateIcon } from '..' import { Providers, Provider } from '../providers'; import { LocalStorage, generatePrefixFromID, clearAllPasses } from '../storage' +function getAllPasses(providerID: number | void): {[key: string]: string[]} { + const response: {[key: string]: string[]} = {}; + + for (const provider of Providers) { + if ((providerID === undefined) || (providerID === provider.ID)) { + try { + const storage = new LocalStorage( + generatePrefixFromID(provider.ID) + ); + + const tokensJSON: string | null = storage.getItem(Provider.TOKEN_STORE_KEY); + + const tokensArray: string[] = (tokensJSON === null) ? [] : JSON.parse(tokensJSON); + + response[ provider.ID.toString() ] = tokensArray; + } + catch (error: any) {} + } + } + + return response; +} + +function addPasses(providerID: string, newTokensArray: string[]): boolean { + try { + const provider: (typeof Provider) | void = Providers.find(p => p.ID.toString() === providerID); + if (provider === undefined) throw 'no matching provider for ID value'; + + const storage = new LocalStorage( + generatePrefixFromID(provider.ID) + ); + + const tokensJSON: string | null = storage.getItem(Provider.TOKEN_STORE_KEY); + + const oldTokensArray: string[] = (tokensJSON === null) ? [] : JSON.parse(tokensJSON); + + const mergedTokensArray: string[] = [ + ...oldTokensArray, + ...(newTokensArray.filter(token => oldTokensArray.indexOf(token) === -1)) + ]; + + storage.setItem( + Provider.TOKEN_STORE_KEY, + JSON.stringify(mergedTokensArray), + ); + + return true; + } + catch (error: any) { + return false; + } +} + export function handleReceivedMessage(request: any, _sender: chrome.runtime.MessageSender, sendResponse: Function): void { // ------------------------------------------------------------------------- - if (request.clear === true) { - clearAllPasses(); + if (request.tokensCount === true) { + const allPasses: {[key: string]: string[]} = getAllPasses(request.providerID); + const response: {[key: string]: number } = {}; - // Update the browser action icon after clearing the tokens. - forceUpdateIcon(); + for (const providerID in allPasses) { + response[providerID] = Number(allPasses[providerID].length); + } + sendResponse(response); return; } // ------------------------------------------------------------------------- - if (request.tokensCount === true) { - const response: {[key: string]: number} = {}; + if (request.backup === true) { + const allPasses: {[key: string]: string[]} = getAllPasses(request.providerID); - for (const provider of Providers) { - if ((typeof request.providerID !== 'number') || (request.providerID === provider.ID)) { - const storage = new LocalStorage( - generatePrefixFromID(provider.ID) - ); - - const tokensJSON: string | null = storage.getItem(Provider.TOKEN_STORE_KEY); - if (tokensJSON === null) continue; + sendResponse(allPasses); + return; + } - try { - const tokensArray: string[] = JSON.parse(tokensJSON); + // ------------------------------------------------------------------------- + if (request.restore === true) { + const backup: {[key: string]: string[]} | void = request.backup; + let did_restore: boolean = false; - response[ provider.ID.toString() ] = Number(tokensArray.length); + if (backup !== undefined) { + for (const providerID in backup) { + if (addPasses(providerID, backup[providerID]) && !did_restore) { + did_restore = true; } - catch (error: any) {} } } - sendResponse(response); + if (did_restore) { + // Update the browser action icon after restoring tokens. + forceUpdateIcon(); + } + + sendResponse(did_restore); + return; + } + + // ------------------------------------------------------------------------- + if (request.clear === true) { + clearAllPasses(); + + // Update the browser action icon after clearing the tokens. + forceUpdateIcon(); + return; } diff --git a/src/popup/components/App/index.tsx b/src/popup/components/App/index.tsx index 7be54fa7..b7955229 100644 --- a/src/popup/components/App/index.tsx +++ b/src/popup/components/App/index.tsx @@ -1,9 +1,11 @@ -import { Container } from '@popup/components/Container'; -import { ClearButton } from '@popup/components/ClearButton'; +import { Header } from '@popup/components/Header'; +import { Container } from '@popup/components/Container'; import { CloudflareButton } from '@popup/components/CloudflareButton'; -import { HcaptchaButton } from '@popup/components/HcaptchaButton'; -import { GithubButton } from '@popup/components/GithubButton'; -import { Header } from '@popup/components/Header'; +import { HcaptchaButton } from '@popup/components/HcaptchaButton'; +import { BackupButton } from '@popup/components/BackupButton'; +import { RestoreButton } from '@popup/components/RestoreButton'; +import { ClearButton } from '@popup/components/ClearButton'; +import { GithubButton } from '@popup/components/GithubButton'; import React from 'react'; import styles from './styles.module.scss'; @@ -14,6 +16,8 @@ export function App(): JSX.Element { + + diff --git a/src/popup/components/BackupButton/index.tsx b/src/popup/components/BackupButton/index.tsx new file mode 100644 index 00000000..69210635 --- /dev/null +++ b/src/popup/components/BackupButton/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; + +import { Button } from '@popup/components/Button'; + +export function BackupButton(): JSX.Element { + const dispatch = useDispatch(); + + const backupPasses = () => { + dispatch({ type: 'BACKUP_TOKENS' }); + }; + return ; +} diff --git a/src/popup/components/RestoreButton/index.tsx b/src/popup/components/RestoreButton/index.tsx new file mode 100644 index 00000000..3b3cf99b --- /dev/null +++ b/src/popup/components/RestoreButton/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; + +import { Button } from '@popup/components/Button'; + +export function RestoreButton(): JSX.Element { + const dispatch = useDispatch(); + + const restorePasses = () => { + dispatch({ type: 'RESTORE_TOKENS' }); + }; + return ; +} diff --git a/src/popup/index.tsx b/src/popup/index.tsx index 33a224cc..6d7dd4e7 100644 --- a/src/popup/index.tsx +++ b/src/popup/index.tsx @@ -13,11 +13,4 @@ ReactDOM.render( document.getElementById('root'), ); -chrome.runtime.sendMessage({ tokensCount: true }, (response: any) => { - if ((response !== undefined) && (response !== null) && (response instanceof Object)) { - store.dispatch({ - type: 'UPDATE_STATE', - value: response - }); - } -}); +store.dispatch({ type: 'OBTAIN_STATE' }); diff --git a/src/popup/store.ts b/src/popup/store.ts index adca5dd7..eb2447e7 100644 --- a/src/popup/store.ts +++ b/src/popup/store.ts @@ -1,25 +1,122 @@ import { createStore } from 'redux'; +interface ObtainStateAction { + type: 'OBTAIN_STATE'; +} + interface UpdateStateAction { type: 'UPDATE_STATE'; value: {[key: string]: number}; } +interface BackupTokensAction { + type: 'BACKUP_TOKENS'; +} + +interface RestoreTokensAction { + type: 'RESTORE_TOKENS'; +} + interface ClearTokensAction { type: 'CLEAR_TOKENS'; } -type Action = UpdateStateAction | ClearTokensAction; +type Action = ObtainStateAction | UpdateStateAction | BackupTokensAction | RestoreTokensAction | ClearTokensAction; const reducer = (state: any | undefined, action: Action) => { state = (state instanceof Object) ? state : {}; switch (action.type) { + case 'OBTAIN_STATE': + chrome.runtime.sendMessage({ tokensCount: true }, (response: any) => { + if ((response !== undefined) && (response !== null) && (response instanceof Object)) { + store.dispatch({ + type: 'UPDATE_STATE', + value: response + }); + } + }); + return state; case 'UPDATE_STATE': return { ...state, ...action.value, }; + case 'BACKUP_TOKENS': + chrome.runtime.sendMessage({ backup: true }, (response: any) => { + if ((response !== undefined) && (response !== null) && (response instanceof Object)) { + try { + // open save-as dialog + + let url: string; + if (window.Blob === undefined || window.URL === undefined) { + url = JSON.stringify(response, null, 2); + url = btoa(url); + url = `data:application/json;base64,${url}`; + } + else { + const blob = new window.Blob([JSON.stringify(response, null, 2)], {type : 'application/json'}); + url = window.URL.createObjectURL(blob); + } + + const anchor = window.document.createElement('a'); + const timestamp = (new Date()).toISOString().replace(/\.\d+Z$/, '').replace('T', '-T').replace(/[:]/g, '-'); + const filename = (`${chrome.i18n.getMessage('appName')}-${chrome.i18n.getMessage('labelFileBackup')}.${timestamp}.json`).replace(/(?:[\/\\\<\>\|\?\*:"]|[\s\r\n])+/g, ''); + anchor.setAttribute('href', url); + anchor.setAttribute('download', filename); + anchor.click(); + } + catch(e) {} + } + }); + return state; + case 'RESTORE_TOKENS': + (async () => { + + const readFile = function (event: Event) { + event.stopPropagation(); + event.stopImmediatePropagation(); + + const input: HTMLInputElement = event.target; + const files: FileList | null = input.files; + if (files === null) return; + + for (let file_index=0; file_index < files.length; file_index++) { + const reader = new FileReader(); + + reader.onload = function(){ + try { + if ((typeof reader.result === 'string') && (reader.result.length > 0)) { + const backupJSON: string = reader.result; + const backup: {[key: string]: string[]} = JSON.parse(backupJSON); + + chrome.runtime.sendMessage({ restore: true, backup }, (response: any) => { + if (response === true) { + store.dispatch({ type: 'OBTAIN_STATE' }); + } + }); + } + } + catch(e) {} + }; + + reader.readAsText( + files[file_index] + ); + } + }; + + try { + const input = window.document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('accept', 'text/plain, application/json, .txt, .json'); + input.addEventListener('change', readFile); + input.click(); + } + catch(e) {} + })(); + + return state; case 'CLEAR_TOKENS': chrome.runtime.sendMessage({ clear: true }); return {}; From b889333da945607e5e7f00e63e03c13e64ce5fc0 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 26 Jan 2022 01:24:19 -0800 Subject: [PATCH 22/36] minor updates to the 'dist/.bin' build scripts --- .../inject_es6_polyfills.bat | 17 +++++++++++++---- .../inject_es6_polyfills.sh | 17 +++++++++++++---- .../chromium/.common/pack_crx_with_chrome.bat | 6 ++++++ .../chromium/.common/pack_crx_with_chrome.sh | 6 ++++++ .../firefox/pack_xpi_with_7zip.bat | 13 ++++++++++--- .../firefox/pack_xpi_with_zip.sh | 13 ++++++++++--- 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat index 4ea3cb24..65aa1843 100755 --- a/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat +++ b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.bat @@ -10,12 +10,21 @@ if not defined ext_name ( exit /b 1 ) -cd "%~dp0..\..\%ext_name%" +set ext_dir="%~dp0..\..\%ext_name%" + +if not exist %ext_dir% ( + echo Extension directory does not exist. + echo Perhaps the Typescript compiler build failed? + echo Quitting without making any changes. + exit /b 1 +) + +cd /D %ext_dir% if exist "%cd%\lib" ( - echo "lib" directory already exists in extension directory - echo has polyfill has already been injected? - echo quitting without making any changes + echo "lib" directory already exists in extension directory. + echo Has polyfill has already been injected? + echo Quitting without making any changes. exit /b 1 ) diff --git a/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.sh b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.sh index a7981ff0..4fad2afd 100755 --- a/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.sh +++ b/dist/.bin/inject ES6 polyfills/inject_es6_polyfills.sh @@ -11,12 +11,21 @@ if [ -z "$ext_name" ];then exit 1 fi -cd "${DIR}/../../${ext_name}" +ext_dir="${DIR}/../../${ext_name}" + +if [ ! -d "$ext_dir" ];then + echo 'Extension directory does not exist.' + echo 'Perhaps the Typescript compiler build failed?' + echo 'Quitting without making any changes.' + exit 1 +fi + +cd "$ext_dir" if [ -d 'lib' ];then - echo '"lib" directory already exists in extension directory' - echo 'has polyfill has already been injected?' - echo 'quitting without making any changes' + echo '"lib" directory already exists in extension directory.' + echo 'Has polyfill has already been injected?' + echo 'Quitting without making any changes.' exit 1 fi diff --git a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat index f7ac6759..58767432 100755 --- a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat +++ b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.bat @@ -11,6 +11,12 @@ cd /D "%~dp0..\..\..\.." set ext_dir="%cd%\%ext_name%" set ext_key="%cd%\%ext_name%.pem" +if not exist %ext_dir% ( + echo Extension directory does not exist. + echo Perhaps the Typescript compiler build failed? + exit /b 1 +) + if exist %ext_key% ( chrome --disable-gpu --disable-software-rasterizer --pack-extension=%ext_dir% --pack-extension-key=%ext_key% ) else ( diff --git a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh index 8e833bb7..df223df5 100755 --- a/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh +++ b/dist/.bin/pack extensions/chromium/.common/pack_crx_with_chrome.sh @@ -17,6 +17,12 @@ function main { ext_dir="${cwd}/${ext_name}" ext_key="${cwd}/${ext_name}.pem" + if [ ! -d "$ext_dir" ];then + echo 'Extension directory does not exist.' + echo 'Perhaps the Typescript compiler build failed?' + exit 1 + fi + if [ -f "$ext_key" ];then chrome --disable-gpu --disable-software-rasterizer "--pack-extension=${ext_dir}" "--pack-extension-key=${ext_key}" else diff --git a/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat b/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat index dd966ecd..3a367686 100755 --- a/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat +++ b/dist/.bin/pack extensions/firefox/pack_xpi_with_7zip.bat @@ -11,13 +11,20 @@ if not defined ext_name ( cd /D "%~dp0..\..\.." -set xpi_file="%cd%\%ext_name%.xpi" +set ext_dir="%cd%\%ext_name%" +set ext_xpi="%cd%\%ext_name%.xpi" -cd "%ext_name%" +if not exist %ext_dir% ( + echo Extension directory does not exist. + echo Perhaps the Typescript compiler build failed? + exit /b 1 +) + +cd %ext_dir% rem :: https://sevenzip.osdn.jp/chm/cmdline/index.htm rem :: https://sevenzip.osdn.jp/chm/cmdline/commands/add.htm -7z a -tzip %xpi_file% -r . +7z a -tzip %ext_xpi% -r . if not defined BUILD_ALL ( echo. diff --git a/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh b/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh index 834e476f..a5811826 100755 --- a/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh +++ b/dist/.bin/pack extensions/firefox/pack_xpi_with_zip.sh @@ -17,12 +17,19 @@ function main { cd "${DIR}/../../.." cwd=$(pwd -P) - xpi_file="${cwd}/${ext_name}.xpi" + ext_dir="${cwd}/${ext_name}" + ext_xpi="${cwd}/${ext_name}.xpi" - cd "$ext_name" + if [ ! -d "$ext_dir" ];then + echo 'Extension directory does not exist.' + echo 'Perhaps the Typescript compiler build failed?' + exit 1 + fi + + cd "$ext_dir" # https://extensionworkshop.com/documentation/publish/package-your-extension/#package-linux - zip -r -FS "$xpi_file" * + zip -r -FS "$ext_xpi" * } main From 8d57b84381ea880062ac6e8ff7797190f288d216 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 26 Jan 2022 18:25:42 -0800 Subject: [PATCH 23/36] fix hCaptcha redemption --- dist/.bin/run_tests.bat | 12 + dist/.bin/run_tests.sh | 9 + package.json | 2 +- public/manifest.json | 2 +- src/background/providers/hcaptcha.test.ts | 257 +++++-------- src/background/providers/hcaptcha.ts | 442 ++++++++++++---------- src/background/providers/provider.ts | 55 ++- 7 files changed, 419 insertions(+), 360 deletions(-) create mode 100644 dist/.bin/run_tests.bat create mode 100644 dist/.bin/run_tests.sh diff --git a/dist/.bin/run_tests.bat b/dist/.bin/run_tests.bat new file mode 100644 index 00000000..c56759ed --- /dev/null +++ b/dist/.bin/run_tests.bat @@ -0,0 +1,12 @@ +@echo off + +call "%~dp0.\.env\build.bat" + +cd /D "%~dp0..\.." + +call npm run test + +if not defined BUILD_ALL ( + echo. + pause +) diff --git a/dist/.bin/run_tests.sh b/dist/.bin/run_tests.sh new file mode 100644 index 00000000..5c8f8c9e --- /dev/null +++ b/dist/.bin/run_tests.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +source "${DIR}/.env/build.sh" + +cd "${DIR}/../.." + +npm run test diff --git a/package.json b/package.json index 852cf6f5..3a6d28cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.5.0", + "version": "3.6.0", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 20d7b09e..a2f384ea 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.5.0", + "version": "3.6.0", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index 2c02743e..d1b27dd8 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -71,30 +71,22 @@ test('getBadgeText', () => { }); /* - * The issuance involves handleBeforeRequest and handleOnCompleted - * listeners. In handleBeforeRequest listener, - * 1. Firstly, the listener check if the request looks like the one that we - * should send an issuance request. - * 2. If it passes the check, the listener returns the cancel command to - * explicitly prevent cancelling the request. - * If not, it returns nothing and let the request continue. - * 3. The listener sets "issueInfo" property which includes the request id - * and other request details. The property will be used by - * handleOnCompleted to issue new tokens. + * The issuance involves handleBeforeRequest and handleOnCompleted listeners. + * + * In handleBeforeRequest listener, + * 1. Check that the request matches the criteria for redemption. + * Such requests are submitting a solved captcha to the provider + * on a website controlled by the provider. + * 2. If so, the listener sets the "issueInfo" property, + * which includes the request id for subsequent processing. * * In handleOnCompleted, - * 1. The listener will check if the provided request id matches the - * request id in "issueInfo". If so, it means that the response is to the - * request checked by handleBeforeRequest that should trigger an issuance request. - * 2. If it passes the check, the listener calls a private method - * "issue" to send an issuance request to the server and the method returns - * an array of issued tokens. - * 3. The listener stores the issued tokens in the storage. - * 4. The listener reloads the tab to get the proper web page for the tab. + * 1. Check that the "issueInfo" property is set, and its request id is a match. + * 2. If so, initiate a secondary request to the provider for the issuing of signed tokens. */ describe('issuance', () => { describe('handleBeforeRequest', () => { - const validDetails = { + const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://hcaptcha.com/checkcaptcha/xxx?s=00000000-0000-0000-0000-000000000000', requestId: 'xxx', @@ -103,9 +95,7 @@ describe('issuance', () => { tabId: 1, type: 'xmlhttprequest' as chrome.webRequest.ResourceType, timeStamp: 1, - requestBody: { - formData: {}, - }, + requestBody: {}, }; test('valid request', async () => { @@ -116,7 +106,7 @@ describe('issuance', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); let result, issueInfo - const reqDetails = validDetails; + const reqDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; result = provider.handleBeforeRequest(reqDetails); expect(result).toEqual({ cancel: false }); @@ -124,7 +114,6 @@ describe('issuance', () => { issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(reqDetails.requestId); expect(issueInfo!.url).toEqual(reqDetails.url); - expect(issueInfo!.formData).toEqual({}); const tokens = [new Token(), new Token(), new Token()]; const issue = jest.fn(async () => { @@ -132,7 +121,7 @@ describe('issuance', () => { }); provider['issue'] = issue; - const resDetails = { + const resDetails: chrome.webRequest.WebResponseHeadersDetails = { ...validDetails, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, @@ -143,7 +132,7 @@ describe('issuance', () => { await Promise.resolve(); expect(issue.mock.calls.length).toBe(1); - expect(issue).toHaveBeenCalledWith(issueInfo!.url, issueInfo!.formData); + expect(issue).toHaveBeenCalledWith(issueInfo!.url); // Expect the tokens are added. const storedTokens = provider['getStoredTokens'](); @@ -176,7 +165,7 @@ describe('issuance', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - const details = { + const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; @@ -198,7 +187,7 @@ describe('issuance', () => { const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - const details = { + const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, url: validDetails.url.replace(/checkcaptcha/g, 'getcaptcha'), }; @@ -216,46 +205,36 @@ describe('issuance', () => { }); /* - * The redemption involves handleHeadersReceived and handleBeforeSendHeaders - * listeners. In handleHeadersReceived listener, - * 1. Firstly, the listener check if the response is the challenge page and - * it supports Privacy Pass redemption. - * 2. If it passes the check, the listener gets a token from the storage to - * redeem. - * 3. The listener sets "redeemInfo" property which includes the request id - * and the mentioned token. The property will be used by - * handleBeforeSendHeaders to redeem the token. - * 4. The listener returns the redirect command so that the browser will - * send the same request again with the token attached. + * The redemption involves handleBeforeRequest and handleBeforeSendHeaders listeners. + * + * In handleBeforeRequest listener, + * 1. Check that the request matches the criteria for redemption. + * Such requests are asking for the provider to generate a new captcha. + * 2. If so, the listener sets the "redeemInfo" property, + * which includes the request id for subsequent processing. * * In handleBeforeSendHeaders, - * 1. The listener will check if the provided request id matches the - * request id in "redeemInfo". If so, it means that the request is from the - * redirect command returned by handleHeadersReceived. If not, it returns - * nothing and let the request continue. - * 2. If it passes the check, the listener attaches the token from - * "redeemInfo" in the "challenge-bypass-token" HTTP header and clears the - * "redeemInfo" property because "redeemInfo" is used already. + * 1. Check that the "redeemInfo" property is set, and its request id is a match. + * 2. If so, add headers to include one token for redemption by provider. */ describe('redemption', () => { - describe('handleHeadersReceived', () => { - const validDetails = { - method: 'GET', - url: 'https://non-issuing-domain.example.com/', + describe('handleBeforeRequest', () => { + const validDetails: chrome.webRequest.WebRequestBodyDetails = { + method: 'POST', + url: 'https://hcaptcha.com/getcaptcha/xxx?s=11111111-1111-1111-1111-111111111111', requestId: 'xxx', frameId: 1, parentFrameId: 1, tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, + type: 'xmlhttprequest' as chrome.webRequest.ResourceType, timeStamp: 1, - statusLine: 'HTTP/1.1 403 Forbidden', - statusCode: 403, - responseHeaders: [ - { - name: 'cf-chl-bypass', - value: '2', - }, - ], + requestBody: { + formData: { + sitekey: ['xxx'], + motionData: ['xxx'], + host: ['non-issuing-domain.example.com'] + } + }, }; test('valid response with tokens', () => { @@ -264,31 +243,51 @@ describe('redemption', () => { const navigateUrl = jest.fn(); const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - const tokens = [new Token(), new Token(), new Token()]; - provider['setStoredTokens'](tokens); + const token = Token.fromString( + '{"input":[238,205,51,250,226,251,144,68,170,68,235,25,231,152,125,63,215,10,42,37,65,157,56,22,98,23,129,9,157,179,223,64],"factor":"0x359953995df006ba98bdcf1383a4c75ca79ae41d4e718dcb051832ce65c002bc","blindedPoint":"BCrzbuVf2eSD/5NtR+o09ovo+oRWAwjwopzl7lb+IuOPuj/ctLkdlkeJQUeyjtUbfgJqU4BFNBRz9ln4z3Dk7Us=","unblindedPoint":"BLKf1op+oq4FcbNdP5vygTkGO3WWLHD6oXCCZDfaFyuFlruih49BStHm6QxtZZAqgCR9i6SsO6VP69hHnfBDNeg=","signed":{"blindedPoint":"BKEnbsQSwnHCxEv4ppp6XuqLV60FiQpF8YWvodQHdnmFHv7CKyWHqBLBW8fJ2uuV+uLxl99+VRYPxr8Q8E7i2Iw=","unblindedPoint":"BA8G3dHM554FzDiOtEsSBu0XYW8p5vA2OIEvnYQcJlRGHTiq2N6j3BKUbiI7I6fAy2vsOrwhrLGHOD+q7YxO+UM="}}', + ); + const tokens = [token, new Token(), new Token()]; + let result, redeemInfo; + provider['setStoredTokens'](tokens); expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = validDetails; - const result = provider.handleHeadersReceived(details); - expect(result).toEqual({ redirectUrl: details.url }); + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; + result = provider.handleBeforeRequest(bodyDetails); + expect(result).toEqual({ cancel: false }); // Expect redeemInfo to be set. - const redeemInfo = provider['redeemInfo']; - expect(redeemInfo!.requestId).toEqual(details.requestId); - expect(redeemInfo!.token.toString()).toEqual(tokens[0].toString()); + redeemInfo = provider['redeemInfo']; + expect(redeemInfo!.requestId).toEqual(bodyDetails.requestId); - expect(updateIcon.mock.calls.length).toBe(2); - expect(updateIcon).toHaveBeenLastCalledWith((tokens.length - 1).toString()); + const headDetails: any = { + ...validDetails, + requestHeaders: [] + }; + delete headDetails.requestBody; - // Expect a token is used. + result = provider.handleBeforeSendHeaders(headDetails); + expect(result).toEqual({ + requestHeaders: [ + { name: 'challenge-bypass-host', value: 'hcaptcha.com' }, + { name: 'challenge-bypass-path', value: 'POST /getcaptcha' }, + { name: 'challenge-bypass-token', value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJhR3ZFRmJaUmN1SnZvcHpSUDBFT1pQb084eDJtdzV6Q3ptUG9mL3AwY3F3PSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==' }, + ], + }); + + // Expect redeemInfo to be unset. + redeemInfo = provider['redeemInfo']; + expect(redeemInfo).toBeNull(); + + // Expect one token to be consumed. const storedTokens = provider['getStoredTokens'](); expect(storedTokens.map((token) => token.toString())).toEqual( tokens.slice(1).map((token) => token.toString()), ); expect(updateIcon.mock.calls.length).toBe(2); + expect(updateIcon).toHaveBeenLastCalledWith((tokens.length - 1).toString()); expect(navigateUrl).not.toHaveBeenCalled(); }); @@ -298,13 +297,22 @@ describe('redemption', () => { const navigateUrl = jest.fn(); const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - provider['setStoredTokens']([]); + let result; + provider['setStoredTokens']([]); expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith('0'); - const details = validDetails; - const result = provider.handleHeadersReceived(details); + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; + result = provider.handleBeforeRequest(bodyDetails); + + const headDetails: any = { + ...validDetails, + requestHeaders: [] + }; + delete headDetails.requestBody; + + result = provider.handleBeforeSendHeaders(headDetails); expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(2); @@ -312,7 +320,7 @@ describe('redemption', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - test('no response from an issuing domain', () => { + test('no response from an issuing domain (hostname)', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -324,23 +332,23 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, - url: 'https://www.hcaptcha.com/privacy-pass', + requestBody: { + formData: { + ...validDetails.requestBody!.formData!, + host: ['www.hcaptcha.com'] + } + } }; - const result = provider.handleHeadersReceived(details); + const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); }); - /* - * The response is invalid if any of the followings is true: - * 1. The status code is not 403. - * 2. There is no HTTP header of "cf-chl-bypass: 1" - */ - test('invalid response w/ wrong status code', () => { + test('no response from an issuing domain (sitekey in body)', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -352,19 +360,23 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, - statusLine: 'HTTP/1.1 200 OK', - statusCode: 200, + requestBody: { + formData: { + ...validDetails.requestBody!.formData!, + sitekey: ['00000000-0000-0000-0000-000000000000'] + } + } }; - const result = provider.handleHeadersReceived(details); + const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); }); - test('invalid response w/ no bypass header', () => { + test('no response from an issuing domain (sitekey in querystring)', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -376,78 +388,15 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, - responseHeaders: [], + url: validDetails.url.replace(/\?s=.*$/, '?s=00000000-0000-0000-0000-000000000000') }; - const result = provider.handleHeadersReceived(details); + const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); }); }); - - describe('handleBeforeSendHeaders', () => { - const validDetails = { - method: 'GET', - url: 'https://www.hcaptcha.com/', - requestId: 'xxx', - frameId: 1, - parentFrameId: 1, - tabId: 1, - type: 'main_frame' as chrome.webRequest.ResourceType, - timeStamp: 1, - requestHeaders: [], - }; - - test('with redeemInfo', () => { - const storage = new StorageMock(); - const updateIcon = jest.fn(); - const navigateUrl = jest.fn(); - - const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - - const token = Token.fromString( - '{"input":[238,205,51,250,226,251,144,68,170,68,235,25,231,152,125,63,215,10,42,37,65,157,56,22,98,23,129,9,157,179,223,64],"factor":"0x359953995df006ba98bdcf1383a4c75ca79ae41d4e718dcb051832ce65c002bc","blindedPoint":"BCrzbuVf2eSD/5NtR+o09ovo+oRWAwjwopzl7lb+IuOPuj/ctLkdlkeJQUeyjtUbfgJqU4BFNBRz9ln4z3Dk7Us=","unblindedPoint":"BLKf1op+oq4FcbNdP5vygTkGO3WWLHD6oXCCZDfaFyuFlruih49BStHm6QxtZZAqgCR9i6SsO6VP69hHnfBDNeg=","signed":{"blindedPoint":"BKEnbsQSwnHCxEv4ppp6XuqLV60FiQpF8YWvodQHdnmFHv7CKyWHqBLBW8fJ2uuV+uLxl99+VRYPxr8Q8E7i2Iw=","unblindedPoint":"BA8G3dHM554FzDiOtEsSBu0XYW8p5vA2OIEvnYQcJlRGHTiq2N6j3BKUbiI7I6fAy2vsOrwhrLGHOD+q7YxO+UM="}}', - ); - const redeemInfo = { - requestId: 'xxx', - token, - }; - provider['redeemInfo'] = redeemInfo; - - const details = validDetails; - const result = provider.handleBeforeSendHeaders(details); - expect(result).toEqual({ - requestHeaders: [ - { - name: 'challenge-bypass-token', - value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJxNmhOM2krakRmQXlpOW1MdjFaSE04alNRSng4SWZKZThWYUIvQU9UYm9FPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', - }, - ], - }); - - const newRedeemInfo = provider['redeemInfo']; - expect(newRedeemInfo).toBeNull(); - - expect(updateIcon).not.toHaveBeenCalled(); - expect(navigateUrl).not.toHaveBeenCalled(); - }); - - test('without redeemInfo', () => { - const storage = new StorageMock(); - const updateIcon = jest.fn(); - const navigateUrl = jest.fn(); - - const provider = new HcaptchaProvider(storage, { updateIcon, navigateUrl }); - - const details = validDetails; - const result = provider.handleBeforeSendHeaders(details); - expect(result).toBeUndefined(); - - expect(updateIcon).not.toHaveBeenCalled(); - expect(navigateUrl).not.toHaveBeenCalled(); - }); - }); }); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index a5819b2e..ce3d9c26 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -8,7 +8,6 @@ import qs from 'qs'; const NUMBER_OF_REQUESTED_TOKENS: number = 5; const DEFAULT_ISSUING_HOSTNAME: string = 'hcaptcha.com'; -const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; @@ -35,6 +34,27 @@ const ALL_ISSUING_CRITERIA: { } } +const ALL_REDEMPTION_CRITERIA: { + HOSTNAMES: QUALIFIED_HOSTNAMES; + PATHNAMES: QUALIFIED_PATHNAMES; + QUERY_PARAMS: QUALIFIED_PARAMS; + BODY_PARAMS: QUALIFIED_PARAMS; +} = { + HOSTNAMES: { + exact : [DEFAULT_ISSUING_HOSTNAME], + contains: [`.${DEFAULT_ISSUING_HOSTNAME}`], + }, + PATHNAMES: { + contains: ['/getcaptcha'], + }, + QUERY_PARAMS: { + some: ['s!=00000000-0000-0000-0000-000000000000'], + }, + BODY_PARAMS: { + every: ['sitekey!=00000000-0000-0000-0000-000000000000', 'motionData', 'host!=www.hcaptcha.com'], + } +} + const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4OifvSTxGcy3T/yac6LVugArFb89 wvqGivp0/54wgeyWkvUZiUdlbIQF7BuGeO9C4sx4nHkpAgRfvd8jdBGz9g== @@ -42,13 +62,11 @@ wvqGivp0/54wgeyWkvUZiUdlbIQF7BuGeO9C4sx4nHkpAgRfvd8jdBGz9g== interface IssueInfo { requestId: string; - url: string; - formData: { [key: string]: string[] | string }; + url: string; } interface RedeemInfo { requestId: string; - token: Token; } export class HcaptchaProvider extends Provider { @@ -95,62 +113,211 @@ export class HcaptchaProvider extends Provider { this.forceUpdateIcon(); } - private async getCommitment(version: string): Promise<{ G: string; H: string }> { - const keyPrefix = 'commitment-'; - const cached = this.storage.getItem(`${keyPrefix}${version}`); - if (cached !== null) { - return JSON.parse(cached); + private getBadgeText(): string { + return this.getStoredTokens().length.toString(); + } + + forceUpdateIcon(): void { + this.callbacks.updateIcon(this.getBadgeText()); + } + + handleActivated(): void { + this.forceUpdateIcon(); + } + + handleBeforeRequest( + details: chrome.webRequest.WebRequestBodyDetails, + ): chrome.webRequest.BlockingResponse | void { + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) + ? details.requestBody.formData + : {} + ; + + if (this.matchesIssuingCriteria(details, url, formData)) { + this.issueInfo = { requestId: details.requestId, url: details.url }; + + // do NOT cancel the request with captcha solution. + return { cancel: false }; } - interface Response { - HC: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; + if (this.matchesRedemptionCriteria(details, url, formData)) { + this.redeemInfo = { requestId: details.requestId }; + + // do NOT cancel the request to generate a new captcha. + // note: "handleBeforeSendHeaders" will add request headers to embed a token. + return { cancel: false }; } + } - // Download the commitment - const { data } = await axios.get(COMMITMENT_URL); - const commitment = data.HC[version]; - if (commitment === undefined) { - throw new Error(`No commitment for the version ${version} is found`); + private matchesIssuingCriteria( + details: chrome.webRequest.WebRequestBodyDetails, + url: URL, + formData: { [key: string]: string[] | string } + ): boolean { + // Only issue tokens for POST requests that contain data in body. + if ( + (details.method.toUpperCase() !== 'POST' ) || + (details.requestBody === null ) || + (details.requestBody === undefined) + ) { + return false; } - let item: { G: string; H: string }; + // Only issue tokens to hosts belonging to the provider. + if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { + return false; + } - // Does the commitment require verification? - if ('G' in commitment) { - item = commitment; + // Only issue tokens when the pathname passes defined criteria. + if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { + return false; } - else { - // Check the expiry date. - const expiry: number = (new Date(commitment.expiry)).getTime(); - if (Date.now() >= expiry) { - throw new Error(`Commitments expired in ${expiry.toString()}`); - } - // This will throw an error on a bad signature. - this.VOPRF.verifyConfiguration( - VERIFICATION_KEY, - { - H: commitment.H, - expiry: commitment.expiry, - }, - commitment.sig, - ); + // Only issue tokens when querystring parameters pass defined criteria. + if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + return false; + } - item = { - G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), - H: commitment.H, - }; + // Only issue tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { + return false; } - // Cache. - this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); + return true; + } - return item; + private matchesRedemptionCriteria( + details: chrome.webRequest.WebRequestBodyDetails, + url: URL, + formData: { [key: string]: string[] | string } + ): boolean { + // Only redeem tokens for POST requests that contain data in body. + if ( + (details.method.toUpperCase() !== 'POST' ) || + (details.requestBody === null ) || + (details.requestBody === undefined) + ) { + return false; + } + + // Only redeem tokens to hosts belonging to the provider. + if (!isIssuingHostname(ALL_REDEMPTION_CRITERIA.HOSTNAMES, url)) { + return false; + } + + // Only redeem tokens when the pathname passes defined criteria. + if (!isQualifiedPathname(ALL_REDEMPTION_CRITERIA.PATHNAMES, url)) { + return false; + } + + // Only redeem tokens when querystring parameters pass defined criteria. + if (!areQualifiedQueryParams(ALL_REDEMPTION_CRITERIA.QUERY_PARAMS, url)) { + return false; + } + + // Only redeem tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_REDEMPTION_CRITERIA.BODY_PARAMS, formData)) { + return false; + } + + return true; + } + + handleBeforeSendHeaders( + details: chrome.webRequest.WebRequestHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + if ( + (this.redeemInfo === null) || + (this.redeemInfo.requestId !== details.requestId) + ) { + return; + } + + // Clear the redeem info. + this.redeemInfo = null; + + // Redeem one token (if available) + + const tokens = this.getStoredTokens(); + const token = tokens.shift(); + this.setStoredTokens(tokens); + + // No tokens in wallet! + if (token === undefined) { + return; + } + + const url = new URL(details.url); + const key = token.getMacKey(); + const binding = this.VOPRF.createRequestBinding(key, [ + voprf.getBytesFromString(url.hostname), + voprf.getBytesFromString(details.method + ' ' + url.pathname), + ]); + + const contents = [ + voprf.getBase64FromBytes(token.getInput()), + binding, + voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), + ]; + const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); + + const headers = details.requestHeaders ?? []; + headers.push( + { name: 'challenge-bypass-host', value: 'hcaptcha.com' }, + { name: 'challenge-bypass-path', value: 'POST /getcaptcha' }, + { name: 'challenge-bypass-token', value: redemption }, + ); + + return { + requestHeaders: headers, + }; + } + + handleHeadersReceived( + _details: chrome.webRequest.WebResponseHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + return; + } + + handleOnCompleted( + details: chrome.webRequest.WebResponseHeadersDetails, + ): void { + this.sendIssueRequest(details.requestId); + } + + handleOnErrorOccurred( + details: chrome.webRequest.WebResponseErrorDetails, + ): void { + this.sendIssueRequest(details.requestId); + } + + private sendIssueRequest(requestId: string): void { + // Is the completed request a trigger to initiate a secondary request to the provider for the issuing of signed tokens? + if ( + (this.issueInfo !== null) && + (this.issueInfo.requestId === requestId) + ) { + const url: string = this.issueInfo!.url; + + // Clear the issue info. + this.issueInfo = null; + + (async () => { + // Issue tokens. + const tokens = await this.issue(url); + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + + this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); + })(); + } } private async issue( url: string, - formData: { [key: string]: string[] | string }, ): Promise { const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token(this.VOPRF)); const issuance = { @@ -160,7 +327,6 @@ export class HcaptchaProvider extends Provider { const param = btoa(JSON.stringify(issuance)); const body = qs.stringify({ - ...formData, [ISSUANCE_BODY_PARAM_NAME]: param, 'captcha-bypass': true, }); @@ -214,172 +380,56 @@ export class HcaptchaProvider extends Provider { return tokens; } - private getBadgeText(): string { - return this.getStoredTokens().length.toString(); - } - - forceUpdateIcon(): void { - this.callbacks.updateIcon(this.getBadgeText()); - } - - handleActivated(): void { - this.forceUpdateIcon(); - } - - handleBeforeSendHeaders( - details: chrome.webRequest.WebRequestHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { - if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { - return; - } - - const url = new URL(details.url); - - const token = this.redeemInfo!.token; - // Clear the redeem info to indicate that we are already redeeming the token. - this.redeemInfo = null; - - const key = token.getMacKey(); - const binding = this.VOPRF.createRequestBinding(key, [ - voprf.getBytesFromString(url.hostname), - voprf.getBytesFromString(details.method + ' ' + url.pathname), - ]); - - const contents = [ - voprf.getBase64FromBytes(token.getInput()), - binding, - voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), - ]; - const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); - - const headers = details.requestHeaders ?? []; - headers.push({ name: 'challenge-bypass-token', value: redemption }); - - return { - requestHeaders: headers, - }; - } - - handleBeforeRequest( - details: chrome.webRequest.WebRequestBodyDetails, - ): chrome.webRequest.BlockingResponse | void { - // Only issue tokens for POST requests that contain 'application/x-www-form-urlencoded' data. - if ( - details.requestBody === null || - details.requestBody === undefined - ) { - return; + private async getCommitment(version: string): Promise<{ G: string; H: string }> { + const keyPrefix = 'commitment-'; + const cached = this.storage.getItem(`${keyPrefix}${version}`); + if (cached !== null) { + return JSON.parse(cached); } - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = details.requestBody.formData || {}; - - // Only issue tokens on the issuing website. - if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { - return; + interface Response { + HC: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; } - // Only issue tokens when the pathname passes defined criteria. - if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { - return; + // Download the commitment + const { data } = await axios.get(COMMITMENT_URL); + const commitment = data.HC[version]; + if (commitment === undefined) { + throw new Error(`No commitment for the version ${version} is found`); } - // Only issue tokens when querystring parameters pass defined criteria. - if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { - return; - } + let item: { G: string; H: string }; - // Only issue tokens when POST data parameters pass defined criteria. - if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { - return; + // Does the commitment require verification? + if ('G' in commitment) { + item = commitment; } - - this.issueInfo = { requestId: details.requestId, url: details.url, formData: {} }; - - for (const key in formData) { - if (Array.isArray(formData[key]) && (formData[key].length === 1)) { - const [value] = formData[key]; - this.issueInfo.formData[key] = value; - } else { - this.issueInfo.formData[key] = formData[key]; + else { + // Check the expiry date. + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { + throw new Error(`Commitments expired in ${expiry.toString()}`); } - } - - // do NOT cancel the original captcha solve request - return { cancel: false }; - } - - handleHeadersReceived( - details: chrome.webRequest.WebResponseHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { - // Don't redeem a token on the issuing website. - if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { - return; - } - // Check if it's the response of the request that we should insert a token. - if (details.statusCode !== 403 || details.responseHeaders === undefined) { - return; - } - const hasSupportHeader = details.responseHeaders.some((header) => { - return ( - header.name.toLowerCase() === CHL_BYPASS_SUPPORT && - header.value !== undefined && - parseInt(header.value, 10) === HcaptchaProvider.ID + // This will throw an error on a bad signature. + this.VOPRF.verifyConfiguration( + VERIFICATION_KEY, + { + H: commitment.H, + expiry: commitment.expiry, + }, + commitment.sig, ); - }); - if (!hasSupportHeader) { - return; - } - - // Let's try to redeem. - - // Get one token. - const tokens = this.getStoredTokens(); - const token = tokens.shift(); - this.setStoredTokens(tokens); - - if (token === undefined) { - return; - } - - this.redeemInfo = { requestId: details.requestId, token }; - // Redirect to resend the request attached with the token. - return { - redirectUrl: details.url, - }; - } - - private sendIssueRequest(requestId: string): void { - // Check if it's the response of the request that solved a captcha on a domain that issues tokens. - if (this.issueInfo !== null && requestId === this.issueInfo.requestId) { - this.issueInfo.requestId = 'issued'; - - (async () => { - // Issue tokens. - const tokens = await this.issue(this.issueInfo!.url, this.issueInfo!.formData); - - // Clear the issue info to indicate that we are already issuing the tokens. - this.issueInfo = null; - - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); - this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); - })(); + item = { + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), + H: commitment.H, + }; } - } - handleOnCompleted( - details: chrome.webRequest.WebResponseHeadersDetails, - ): void { - this.sendIssueRequest(details.requestId); - } + // Cache. + this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); - handleOnErrorOccurred( - details: chrome.webRequest.WebResponseErrorDetails, - ): void { - this.sendIssueRequest(details.requestId); + return item; } } diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts index b7d6bd95..3fdeb7a7 100644 --- a/src/background/providers/provider.ts +++ b/src/background/providers/provider.ts @@ -101,10 +101,28 @@ function areQualifiedParamsFound(params: QUALIFIED_PARAMS | void, test: (param: } function isQualifiedQueryParam(url: URL, param: string): boolean { - const [param_name, param_value] = param.split('=', 2); + let [param_name, param_value] = param.split('=', 2); + let param_exact: boolean = true; + if (param_value) { + if (param_name[param_name.length - 1] === '!') { + param_exact = false; + param_name = param_name.substring(0, param_name.length - 1); + } + } + if (!url.searchParams.has(param_name)) return false; - if (param_value && (url.searchParams.get(param_name) !== param_value)) return false; - return true; + + if (param_value) { + const actual_value: string | null = url.searchParams.get(param_name); + + return param_exact + ? (actual_value! === param_value) + : (actual_value! !== param_value) + ; + } + else { + return true; + } } export function areQualifiedQueryParams(params: QUALIFIED_PARAMS | void, url: URL): boolean { @@ -115,17 +133,38 @@ export function areQualifiedQueryParams(params: QUALIFIED_PARAMS | void, url: UR function isQualifiedBodyFormParam(formData: { [key: string]: string[] | string } | void, param: string): boolean { if (!(formData instanceof Object)) return false; - const [param_name, param_value] = param.split('=', 2); + let [param_name, param_value] = param.split('=', 2); + let param_exact: boolean = true; + if (param_value) { + if (param_name[param_name.length - 1] === '!') { + param_exact = false; + param_name = param_name.substring(0, param_name.length - 1); + } + } + if (!(param_name in formData)) return false; + if (param_value) { - if (Array.isArray(formData[param_name])) { - if (formData[param_name].indexOf(param_value) === -1) return false; + const actual_value: string | string[] = formData[param_name]; + + if (Array.isArray(actual_value)) { + const isInArray: boolean = (actual_value.indexOf(param_value) !== -1); + + return param_exact + ? isInArray + : !isInArray + ; } else { - if (formData[param_name] !== param_value) return false; + return param_exact + ? (actual_value === param_value) + : (actual_value !== param_value) + ; } } - return true; + else { + return true; + } } export function areQualifiedBodyFormParams(params: QUALIFIED_PARAMS | void, formData: { [key: string]: string[] | string } | void): boolean { From 1f84b013598875ec584ba304a1e79a6e73db356e Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 27 Jan 2022 00:51:57 -0800 Subject: [PATCH 24/36] minor refactoring --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.ts | 276 +++++++++++++------------ src/background/providers/hcaptcha.ts | 2 +- src/popup/store.ts | 2 +- 5 files changed, 150 insertions(+), 134 deletions(-) diff --git a/package.json b/package.json index 3a6d28cb..63373719 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.0", + "version": "3.6.1", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index a2f384ea..43251c02 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.0", + "version": "3.6.1", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index f284fe8f..6215baab 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -87,57 +87,90 @@ export class CloudflareProvider extends Provider { this.forceUpdateIcon(); } - private async getCommitment(version: string): Promise<{ G: string; H: string }> { - const keyPrefix = 'commitment-'; - const cached = this.storage.getItem(`${keyPrefix}${version}`); - if (cached !== null) { - return JSON.parse(cached); - } + private getBadgeText(): string { + return this.getStoredTokens().length.toString(); + } - interface Response { - CF: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; - } + forceUpdateIcon(): void { + this.callbacks.updateIcon(this.getBadgeText()); + } - // Download the commitment - const { data } = await axios.get(COMMITMENT_URL); - const commitment = data.CF[version]; - if (commitment === undefined) { - throw new Error(`No commitment for the version ${version} is found`); + handleActivated(): void { + this.forceUpdateIcon(); + } + + handleBeforeRequest( + details: chrome.webRequest.WebRequestBodyDetails, + ): chrome.webRequest.BlockingResponse | void { + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) + ? details.requestBody.formData + : {} + ; + + if (this.matchesIssuingCriteria(details, url, formData)) { + (async (): Promise => { + // Normalize 'application/x-www-form-urlencoded' data parameters in POST body + const flattenFormData: { [key: string]: string[] | string } = {}; + for (const key in formData) { + if (Array.isArray(formData[key]) && (formData[key].length === 1)) { + const [value] = formData[key]; + flattenFormData[key] = value; + } else { + flattenFormData[key] = formData[key]; + } + } + + // Issue tokens. + const tokens = await this.issue(details.url, flattenFormData); + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + + this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + })(); + + // safe to cancel + return { cancel: true }; } + } - let item: { G: string; H: string }; + private matchesIssuingCriteria( + details: chrome.webRequest.WebRequestBodyDetails, + url: URL, + formData: { [key: string]: string[] | string } + ): boolean { + // Only issue tokens for POST requests that contain data in body. + if ( + (details.method.toUpperCase() !== 'POST' ) || + (details.requestBody === null ) || + (details.requestBody === undefined) + ) { + return false; + } - // Does the commitment require verification? - if ('G' in commitment) { - item = commitment; + // Only issue tokens to hosts belonging to the provider. + if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { + return false; } - else { - // Check the expiry date. - const expiry: number = (new Date(commitment.expiry)).getTime(); - if (Date.now() >= expiry) { - throw new Error(`Commitments expired in ${expiry.toString()}`); - } - // This will throw an error on a bad signature. - this.VOPRF.verifyConfiguration( - VERIFICATION_KEY, - { - H: commitment.H, - expiry: commitment.expiry, - }, - commitment.sig, - ); + // Only issue tokens when the pathname passes defined criteria. + if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { + return false; + } - item = { - G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), - H: commitment.H, - }; + // Only issue tokens when querystring parameters pass defined criteria. + if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + return false; } - // Cache. - this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); + // Only issue tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { + return false; + } - return item; + return true; } private async issue( @@ -205,109 +238,57 @@ export class CloudflareProvider extends Provider { return tokens; } - private getBadgeText(): string { - return this.getStoredTokens().length.toString(); - } - - forceUpdateIcon(): void { - this.callbacks.updateIcon(this.getBadgeText()); - } - - handleActivated(): void { - this.forceUpdateIcon(); - } - - handleBeforeSendHeaders( - details: chrome.webRequest.WebRequestHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { - if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { - return; - } - - const url = new URL(details.url); - - const token = this.redeemInfo!.token; - // Clear the redeem info to indicate that we are already redeeming the token. - this.redeemInfo = null; - - const key = token.getMacKey(); - const binding = this.VOPRF.createRequestBinding(key, [ - voprf.getBytesFromString(url.hostname), - voprf.getBytesFromString(details.method + ' ' + url.pathname), - ]); - - const contents = [ - voprf.getBase64FromBytes(token.getInput()), - binding, - voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), - ]; - const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); - - const headers = details.requestHeaders ?? []; - headers.push({ name: 'challenge-bypass-token', value: redemption }); - - return { - requestHeaders: headers, - }; - } - - handleBeforeRequest( - details: chrome.webRequest.WebRequestBodyDetails, - ): chrome.webRequest.BlockingResponse | void { - // Only issue tokens for POST requests that contain 'application/x-www-form-urlencoded' data. - if ( - details.requestBody === null || - details.requestBody === undefined - ) { - return; + private async getCommitment(version: string): Promise<{ G: string; H: string }> { + const keyPrefix = 'commitment-'; + const cached = this.storage.getItem(`${keyPrefix}${version}`); + if (cached !== null) { + return JSON.parse(cached); } - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = details.requestBody.formData || {}; - - // Only issue tokens on the issuing website. - if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { - return; + interface Response { + CF: { [version: string]: { G: string; H: string } | { H: string; expiry: string; sig: string } }; } - // Only issue tokens when the pathname passes defined criteria. - if (!isQualifiedPathname(ALL_ISSUING_CRITERIA.PATHNAMES, url)) { - return; + // Download the commitment + const { data } = await axios.get(COMMITMENT_URL); + const commitment = data.CF[version]; + if (commitment === undefined) { + throw new Error(`No commitment for the version ${version} is found`); } - // Only issue tokens when querystring parameters pass defined criteria. - if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { - return; - } + let item: { G: string; H: string }; - // Only issue tokens when POST data parameters pass defined criteria. - if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { - return; + // Does the commitment require verification? + if ('G' in commitment) { + item = commitment; } - - const flattenFormData: { [key: string]: string[] | string } = {}; - for (const key in formData) { - if (Array.isArray(formData[key]) && (formData[key].length === 1)) { - const [value] = formData[key]; - flattenFormData[key] = value; - } else { - flattenFormData[key] = formData[key]; + else { + // Check the expiry date. + const expiry: number = (new Date(commitment.expiry)).getTime(); + if (Date.now() >= expiry) { + throw new Error(`Commitments expired in ${expiry.toString()}`); } - } - (async () => { - // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); + // This will throw an error on a bad signature. + this.VOPRF.verifyConfiguration( + VERIFICATION_KEY, + { + H: commitment.H, + expiry: commitment.expiry, + }, + commitment.sig, + ); - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); + item = { + G: voprf.sec1EncodeToBase64(this.VOPRF.getActiveECSettings().curve.G, false), + H: commitment.H, + }; + } - this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); - })(); + // Cache. + this.storage.setItem(`${keyPrefix}${version}`, JSON.stringify(item)); - // safe to cancel - return { cancel: true }; + return item; } handleHeadersReceived( @@ -345,12 +326,47 @@ export class CloudflareProvider extends Provider { } this.redeemInfo = { requestId: details.requestId, token }; + // Redirect to resend the request attached with the token. return { redirectUrl: details.url, }; } + handleBeforeSendHeaders( + details: chrome.webRequest.WebRequestHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { + return; + } + + const token = this.redeemInfo!.token; + + // Clear the redeem info. + this.redeemInfo = null; + + const url = new URL(details.url); + const key = token.getMacKey(); + const binding = this.VOPRF.createRequestBinding(key, [ + voprf.getBytesFromString(url.hostname), + voprf.getBytesFromString(details.method + ' ' + url.pathname), + ]); + + const contents = [ + voprf.getBase64FromBytes(token.getInput()), + binding, + voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), + ]; + const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); + + const headers = details.requestHeaders ?? []; + headers.push({ name: 'challenge-bypass-token', value: redemption }); + + return { + requestHeaders: headers, + }; + } + handleOnCompleted( _details: chrome.webRequest.WebResponseHeadersDetails, ): void { diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index ce3d9c26..31405bf9 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -303,7 +303,7 @@ export class HcaptchaProvider extends Provider { // Clear the issue info. this.issueInfo = null; - (async () => { + (async (): Promise => { // Issue tokens. const tokens = await this.issue(url); diff --git a/src/popup/store.ts b/src/popup/store.ts index eb2447e7..f3e3bbd0 100644 --- a/src/popup/store.ts +++ b/src/popup/store.ts @@ -71,7 +71,7 @@ const reducer = (state: any | undefined, action: Action) => { }); return state; case 'RESTORE_TOKENS': - (async () => { + (async (): Promise => { const readFile = function (event: Event) { event.stopPropagation(); From bf6d1ae8c684854f6a6bf80e92f6281c57bd4183 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Thu, 27 Jan 2022 21:58:03 -0800 Subject: [PATCH 25/36] address race-condition: delay blocking async code by next-tick timer context: ======== * "handleBeforeRequest" is a hook function implemented by each provider * a global "chrome.webRequest.onBeforeRequest" listener delegates to the hook function implemented by the active provider * the listener blocks the sending of all outbound requests, so it needs to perform its work quickly previous methodology: ===================== * any code in the hook function that is required to produce a return value is performed synchronously * all other code is performed asynchronously: (async () => { // work that is time consuming // and does not contribute to a return value })() new methodology: ================ * all such asynchronous code has been delayed by a timer to begin execution in the next tick of the event loop: setTimeout( async () => {}, 0 ) considerations: =============== * the previous methodology created a race-condition by delaying outbound requests * the new methodology creates a different race-condition by delaying the processing of code, which may not be finished at the time its result is needed observations: ============= * the previous methodology prevented the Cloudflare provider from being able to issue tokens about 90% of the time * the new methodology prevents the Cloudflare provider from being able to issue tokens about 10% of the time additional observations: ======================== * issuance and redemption by the hCaptcha provider is not impacted by either methodology * redemption by the Cloudflare provider is not impacted by either methodology conclusions: ============ * while this isn't a perfect solution, it is a significant improvement to the user experience --- src/background/providers/cloudflare.test.ts | 19 ++++++ src/background/providers/cloudflare.ts | 38 ++++++------ src/background/providers/hcaptcha.test.ts | 65 +++++++++++++++++++-- src/background/providers/hcaptcha.ts | 59 +++++++++---------- 4 files changed, 129 insertions(+), 52 deletions(-) diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index e6806855..8807c63d 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -85,6 +85,17 @@ test('getBadgeText', () => { */ describe('issuance', () => { describe('handleBeforeRequest', () => { + + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + const validDetails = { method: 'POST', url: 'https://captcha.website/?__cf_chl_captcha_tk__=query-param', @@ -115,6 +126,10 @@ describe('issuance', () => { const result = provider.handleBeforeRequest(validDetails); expect(result).toEqual({ cancel: true }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); await Promise.resolve(); expect(issue.mock.calls.length).toBe(1); @@ -160,6 +175,8 @@ describe('issuance', () => { }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); + + expect(setTimeout).toHaveBeenCalledTimes(0); await Promise.resolve(); expect(issue).not.toHaveBeenCalled(); @@ -182,6 +199,8 @@ describe('issuance', () => { }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); + + expect(setTimeout).toHaveBeenCalledTimes(0); await Promise.resolve(); expect(issue).not.toHaveBeenCalled(); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 6215baab..cb8cca97 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -109,27 +109,31 @@ export class CloudflareProvider extends Provider { ; if (this.matchesIssuingCriteria(details, url, formData)) { - (async (): Promise => { - // Normalize 'application/x-www-form-urlencoded' data parameters in POST body - const flattenFormData: { [key: string]: string[] | string } = {}; - for (const key in formData) { - if (Array.isArray(formData[key]) && (formData[key].length === 1)) { - const [value] = formData[key]; - flattenFormData[key] = value; - } else { - flattenFormData[key] = formData[key]; + + setTimeout( + async (): Promise => { + // Normalize 'application/x-www-form-urlencoded' data parameters in POST body + const flattenFormData: { [key: string]: string[] | string } = {}; + for (const key in formData) { + if (Array.isArray(formData[key]) && (formData[key].length === 1)) { + const [value] = formData[key]; + flattenFormData[key] = value; + } else { + flattenFormData[key] = formData[key]; + } } - } - // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); + // Issue tokens. + const tokens = await this.issue(details.url, flattenFormData); - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); - this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); - })(); + this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + }, + 0 + ); // safe to cancel return { cancel: true }; diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index d1b27dd8..75fff31e 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -86,6 +86,17 @@ test('getBadgeText', () => { */ describe('issuance', () => { describe('handleBeforeRequest', () => { + + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://hcaptcha.com/checkcaptcha/xxx?s=00000000-0000-0000-0000-000000000000', @@ -110,6 +121,10 @@ describe('issuance', () => { result = provider.handleBeforeRequest(reqDetails); expect(result).toEqual({ cancel: false }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); + // Expect issueInfo to be set. issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(reqDetails.requestId); @@ -170,7 +185,11 @@ describe('issuance', () => { url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; const result = provider.handleBeforeRequest(details); - expect(result).toBeUndefined(); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); // Expect issueInfo to be null. const issueInfo = provider['issueInfo']; @@ -192,7 +211,11 @@ describe('issuance', () => { url: validDetails.url.replace(/checkcaptcha/g, 'getcaptcha'), }; const result = provider.handleBeforeRequest(details); - expect(result).toBeUndefined(); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); // Expect issueInfo to be null. const issueInfo = provider['issueInfo']; @@ -219,6 +242,17 @@ describe('issuance', () => { */ describe('redemption', () => { describe('handleBeforeRequest', () => { + + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(global, 'setTimeout'); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://hcaptcha.com/getcaptcha/xxx?s=11111111-1111-1111-1111-111111111111', @@ -257,6 +291,10 @@ describe('redemption', () => { result = provider.handleBeforeRequest(bodyDetails); expect(result).toEqual({ cancel: false }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); + // Expect redeemInfo to be set. redeemInfo = provider['redeemInfo']; expect(redeemInfo!.requestId).toEqual(bodyDetails.requestId); @@ -305,6 +343,11 @@ describe('redemption', () => { const bodyDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; result = provider.handleBeforeRequest(bodyDetails); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); const headDetails: any = { ...validDetails, @@ -342,7 +385,11 @@ describe('redemption', () => { } }; const result = provider.handleBeforeRequest(details); - expect(result).toBeUndefined(); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); @@ -370,7 +417,11 @@ describe('redemption', () => { } }; const result = provider.handleBeforeRequest(details); - expect(result).toBeUndefined(); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); @@ -393,7 +444,11 @@ describe('redemption', () => { url: validDetails.url.replace(/\?s=.*$/, '?s=00000000-0000-0000-0000-000000000000') }; const result = provider.handleBeforeRequest(details); - expect(result).toBeUndefined(); + expect(result).toEqual({ cancel: false }); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 31405bf9..b1903abc 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -128,26 +128,29 @@ export class HcaptchaProvider extends Provider { handleBeforeRequest( details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) - ? details.requestBody.formData - : {} - ; - - if (this.matchesIssuingCriteria(details, url, formData)) { - this.issueInfo = { requestId: details.requestId, url: details.url }; - - // do NOT cancel the request with captcha solution. - return { cancel: false }; - } - if (this.matchesRedemptionCriteria(details, url, formData)) { - this.redeemInfo = { requestId: details.requestId }; + setTimeout( + (): void => { + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) + ? details.requestBody.formData + : {} + ; + + if (this.matchesIssuingCriteria(details, url, formData)) { + this.issueInfo = { requestId: details.requestId, url: details.url }; + return; + } + + if (this.matchesRedemptionCriteria(details, url, formData)) { + this.redeemInfo = { requestId: details.requestId }; + return; + } + }, + 0 + ); - // do NOT cancel the request to generate a new captcha. - // note: "handleBeforeSendHeaders" will add request headers to embed a token. - return { cancel: false }; - } + return { cancel: false }; } private matchesIssuingCriteria( @@ -292,7 +295,7 @@ export class HcaptchaProvider extends Provider { this.sendIssueRequest(details.requestId); } - private sendIssueRequest(requestId: string): void { + private async sendIssueRequest(requestId: string): Promise { // Is the completed request a trigger to initiate a secondary request to the provider for the issuing of signed tokens? if ( (this.issueInfo !== null) && @@ -303,22 +306,18 @@ export class HcaptchaProvider extends Provider { // Clear the issue info. this.issueInfo = null; - (async (): Promise => { - // Issue tokens. - const tokens = await this.issue(url); + // Issue tokens. + const tokens = await this.issue(url); - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); - this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); - })(); + this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); } } - private async issue( - url: string, - ): Promise { + private async issue(url: string): Promise { const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token(this.VOPRF)); const issuance = { type: 'Issue', From 48f5e145df709aa60a6859bb6e2e9da994a9eebd Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Fri, 28 Jan 2022 14:09:08 -0800 Subject: [PATCH 26/36] revert hCaptcha and refactor Cloudflare the issue of a race-condition persists * the previous commit didn't improve the situation as much as it initially seemed * this commit rolls back some changes that I dislike, and makes a few minor improvements to the code, but doesn't solve the issue --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.test.ts | 4 ++ src/background/providers/cloudflare.ts | 65 ++++++++++++--------- src/background/providers/hcaptcha.test.ts | 65 ++------------------- src/background/providers/hcaptcha.ts | 39 ++++++------- 6 files changed, 66 insertions(+), 111 deletions(-) diff --git a/package.json b/package.json index 63373719..75a2401b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.1", + "version": "3.6.2", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 43251c02..6937e0bf 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.1", + "version": "3.6.2", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 8807c63d..68e1f137 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -396,6 +396,10 @@ describe('redemption', () => { requestHeaders: [], }; + afterEach(() => { + validDetails.requestHeaders = []; + }); + test('with redeemInfo', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index cb8cca97..a3e97207 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -109,28 +109,9 @@ export class CloudflareProvider extends Provider { ; if (this.matchesIssuingCriteria(details, url, formData)) { - setTimeout( - async (): Promise => { - // Normalize 'application/x-www-form-urlencoded' data parameters in POST body - const flattenFormData: { [key: string]: string[] | string } = {}; - for (const key in formData) { - if (Array.isArray(formData[key]) && (formData[key].length === 1)) { - const [value] = formData[key]; - flattenFormData[key] = value; - } else { - flattenFormData[key] = formData[key]; - } - } - - // Issue tokens. - const tokens = await this.issue(details.url, flattenFormData); - - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); - - this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + (): void => { + this.sendIssueRequest(details.url, formData); }, 0 ); @@ -143,7 +124,7 @@ export class CloudflareProvider extends Provider { private matchesIssuingCriteria( details: chrome.webRequest.WebRequestBodyDetails, url: URL, - formData: { [key: string]: string[] | string } + formData: { [key: string]: string[] | string }, ): boolean { // Only issue tokens for POST requests that contain data in body. if ( @@ -177,8 +158,38 @@ export class CloudflareProvider extends Provider { return true; } + private async sendIssueRequest( + url: string, + formData: { [key: string]: string[] | string }, + ): Promise { + try { + // Normalize 'application/x-www-form-urlencoded' data parameters in POST body + const flattenFormData: { [key: string]: string[] | string } = {}; + for (const key in formData) { + if (Array.isArray(formData[key]) && (formData[key].length === 1)) { + const [value] = formData[key]; + flattenFormData[key] = value; + } else { + flattenFormData[key] = formData[key]; + } + } + + // Issue tokens. + const tokens = await this.issue(url, flattenFormData); + + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + } + catch(error: any) { + console.error(error.message); + } + + this.callbacks.navigateUrl(CloudflareProvider.EARNED_TOKEN_COOKIE.url); + } + private async issue( - url: string, + url: string, formData: { [key: string]: string[] | string }, ): Promise { const tokens = Array.from(Array(NUMBER_OF_REQUESTED_TOKENS).keys()).map(() => new Token(this.VOPRF)); @@ -318,13 +329,13 @@ export class CloudflareProvider extends Provider { return; } - // Let's try to redeem. + // Redeem one token (if available) - // Get one token. const tokens = this.getStoredTokens(); const token = tokens.shift(); this.setStoredTokens(tokens); + // No tokens in wallet! if (token === undefined) { return; } @@ -366,9 +377,7 @@ export class CloudflareProvider extends Provider { const headers = details.requestHeaders ?? []; headers.push({ name: 'challenge-bypass-token', value: redemption }); - return { - requestHeaders: headers, - }; + return {requestHeaders: headers}; } handleOnCompleted( diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index 75fff31e..d1b27dd8 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -86,17 +86,6 @@ test('getBadgeText', () => { */ describe('issuance', () => { describe('handleBeforeRequest', () => { - - beforeEach(() => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout'); - }); - - afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); - }); - const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://hcaptcha.com/checkcaptcha/xxx?s=00000000-0000-0000-0000-000000000000', @@ -121,10 +110,6 @@ describe('issuance', () => { result = provider.handleBeforeRequest(reqDetails); expect(result).toEqual({ cancel: false }); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); - // Expect issueInfo to be set. issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(reqDetails.requestId); @@ -185,11 +170,7 @@ describe('issuance', () => { url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + expect(result).toBeUndefined(); // Expect issueInfo to be null. const issueInfo = provider['issueInfo']; @@ -211,11 +192,7 @@ describe('issuance', () => { url: validDetails.url.replace(/checkcaptcha/g, 'getcaptcha'), }; const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + expect(result).toBeUndefined(); // Expect issueInfo to be null. const issueInfo = provider['issueInfo']; @@ -242,17 +219,6 @@ describe('issuance', () => { */ describe('redemption', () => { describe('handleBeforeRequest', () => { - - beforeEach(() => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout'); - }); - - afterEach(() => { - jest.clearAllTimers(); - jest.useRealTimers(); - }); - const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://hcaptcha.com/getcaptcha/xxx?s=11111111-1111-1111-1111-111111111111', @@ -291,10 +257,6 @@ describe('redemption', () => { result = provider.handleBeforeRequest(bodyDetails); expect(result).toEqual({ cancel: false }); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); - // Expect redeemInfo to be set. redeemInfo = provider['redeemInfo']; expect(redeemInfo!.requestId).toEqual(bodyDetails.requestId); @@ -343,11 +305,6 @@ describe('redemption', () => { const bodyDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; result = provider.handleBeforeRequest(bodyDetails); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); const headDetails: any = { ...validDetails, @@ -385,11 +342,7 @@ describe('redemption', () => { } }; const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); @@ -417,11 +370,7 @@ describe('redemption', () => { } }; const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); @@ -444,11 +393,7 @@ describe('redemption', () => { url: validDetails.url.replace(/\?s=.*$/, '?s=00000000-0000-0000-0000-000000000000') }; const result = provider.handleBeforeRequest(details); - expect(result).toEqual({ cancel: false }); - - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); - jest.runAllTimers(); + expect(result).toBeUndefined(); expect(updateIcon.mock.calls.length).toBe(1); expect(navigateUrl).not.toHaveBeenCalled(); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index b1903abc..c2048a75 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -128,29 +128,26 @@ export class HcaptchaProvider extends Provider { handleBeforeRequest( details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { + const url = new URL(details.url); + const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) + ? details.requestBody.formData + : {} + ; - setTimeout( - (): void => { - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) - ? details.requestBody.formData - : {} - ; - - if (this.matchesIssuingCriteria(details, url, formData)) { - this.issueInfo = { requestId: details.requestId, url: details.url }; - return; - } - - if (this.matchesRedemptionCriteria(details, url, formData)) { - this.redeemInfo = { requestId: details.requestId }; - return; - } - }, - 0 - ); + if (this.matchesIssuingCriteria(details, url, formData)) { + this.issueInfo = { requestId: details.requestId, url: details.url }; + + // do NOT cancel the request with captcha solution. + return { cancel: false }; + } + + if (this.matchesRedemptionCriteria(details, url, formData)) { + this.redeemInfo = { requestId: details.requestId }; - return { cancel: false }; + // do NOT cancel the request to generate a new captcha. + // note: "handleBeforeSendHeaders" will add request headers to embed a token. + return { cancel: false }; + } } private matchesIssuingCriteria( From 71e4b5494e8745e40896aac3d0a7798e8ec00b68 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Fri, 28 Jan 2022 20:58:50 -0800 Subject: [PATCH 27/36] detect and fix incorrect requests on Cloudflare issuing domain This methodology is aggressive. It doesn't aim to fix the race-condition that exists on the Cloudflare website; only Cloudflare can do that directly. It provides a workaround to deal with both possible outcomes. One outcome is desirable, and everything works as it should. The other outcome is unexpected, and this commit: * detects the incorrect request * cancels the incorrect request * cherry picks data from its headers * constructs the correct request to issue tokens This methodology is very similar to how a correct request is processed. The difference is where the data originates, which is then used to construct the request to issue tokens. a "correct" request: ==================== curl 'https://captcha.website/?__cf_chl_captcha_tk__=xxx' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Referer: https://captcha.website/' \ --data-raw 'md=xxx&r=xxx&cf_captcha_kind=h&vc=xxx&captcha_vc=xxx&captcha_answer=xxx&cf_ch_verify=plat&h-captcha-response=captchka' an "incorrect" request: ======================= curl 'https://captcha.website/' \ -H 'content-type: application/x-www-form-urlencoded' \ -H 'referer: https://captcha.website/?__cf_chl_tk=xxx' \ --data-raw 'md=xxx&r=xxx&cf_captcha_kind=h&vc=xxx&captcha_vc=xxx&captcha_answer=xxx&cf_ch_verify=plat&h-captcha-response=captchka' In a "correct" request: * querystring of url contains value for key: '__cf_chl_captcha_tk__' In an "incorrect" request, * querystring of 'referer' header contains value for key: '__cf_chl_tk' * the "correct" url can be constructed by adding this value to the querystring key: '__cf_chl_captcha_tk__' * apparently, they are interchangeable! - the resulting request to issue tokens works 100% of the time --- src/background/index.ts | 4 + src/background/providers/cloudflare.test.ts | 249 ++++++++++++++------ src/background/providers/cloudflare.ts | 241 ++++++++++++------- src/background/providers/hcaptcha.test.ts | 25 +- src/background/providers/hcaptcha.ts | 25 +- 5 files changed, 364 insertions(+), 180 deletions(-) diff --git a/src/background/index.ts b/src/background/index.ts index 780d0657..898ca2c5 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -75,20 +75,24 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs: chrome. chrome.webRequest.onBeforeRequest.addListener(handleBeforeRequest, { urls: [''] }, [ 'blocking', 'requestBody', + 'extraHeaders', ]); chrome.webRequest.onBeforeSendHeaders.addListener(handleBeforeSendHeaders, { urls: [''] }, [ 'blocking', 'requestHeaders', + 'extraHeaders', ]); chrome.webRequest.onHeadersReceived.addListener(handleHeadersReceived, { urls: [''] }, [ 'blocking', 'responseHeaders', + 'extraHeaders', ]); chrome.webRequest.onCompleted.addListener(handleOnCompleted, { urls: [''] }, [ 'responseHeaders', + 'extraHeaders', ]); chrome.webRequest.onErrorOccurred.addListener(handleOnErrorOccurred, { urls: [''] }); diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 68e1f137..6a0d75b9 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -71,17 +71,21 @@ test('getBadgeText', () => { }); /* - * The issuance involves handleBeforeRequest listener. - * 1. Firstly, the listener check if the request looks like the one that we - * should send an issuance request. - * 2. If it passes the check, the listener returns the cancel command to - * cancel the request. If not, it returns nothing and let the request - * continue. - * 3. At the same time the listener returns, it calls a private method - * "issue" to send an issuance request to the server and the method return - * an array of issued tokens. - * 4. The listener stored the issued tokens in the storage. - * 5. The listener reloads the tab to get the proper web page for the tab.); + * The issuance involves handleBeforeRequest and handleBeforeSendHeaders listeners. + * + * In handleBeforeRequest listener, + * 1. Check that the request matches the criteria for issuing new tokens. + * Such requests are submitting a solved captcha to the provider + * on a website controlled by the provider. + * 2. If so, the listener sets the "issueInfo" property, + * which includes the request id for subsequent processing. + * + * In handleBeforeSendHeaders, + * 1. Check that the "issueInfo" property is set, and its request id is a match. + * 2. If so, complete the check to determine that the request matches the criteria for issuing new tokens. + * 3. If so, + * 3a. Cancel the original request. + * 3b. Initiate a secondary request to the provider for the issuing of signed tokens. */ describe('issuance', () => { describe('handleBeforeRequest', () => { @@ -96,7 +100,7 @@ describe('issuance', () => { jest.useRealTimers(); }); - const validDetails = { + const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', url: 'https://captcha.website/?__cf_chl_captcha_tk__=query-param', requestId: 'xxx', @@ -123,17 +127,102 @@ describe('issuance', () => { return tokens; }); provider['issue'] = issue; + let result, issueInfo; + + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = validDetails; + result = provider.handleBeforeRequest(bodyDetails); + expect(result).toEqual({ cancel: false }); + + // Expect issueInfo to be set. + issueInfo = provider['issueInfo']; + expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); + expect(issueInfo!.url).toEqual(bodyDetails.url); + expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + + const headDetails: any = { + ...validDetails, + requestHeaders: [] + }; + delete headDetails.requestBody; + + result = provider.handleBeforeSendHeaders(headDetails); + expect(result).toEqual({ cancel: true }); + + // Expect issueInfo to be unset. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); + jest.runAllTimers(); + await Promise.resolve(); + + expect(issue.mock.calls.length).toBe(1); + expect(issue).toHaveBeenCalledWith(headDetails.url, { + 'h-captcha-response': 'body-param', + }); + + // Expect the tokens are added. + const storedTokens = provider['getStoredTokens'](); + expect(storedTokens.map((token) => token.toString())).toEqual( + tokens.map((token) => token.toString()), + ); + + expect(updateIcon.mock.calls.length).toBe(1); + expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); + + expect(navigateUrl.mock.calls.length).toBe(1); + expect(navigateUrl).toHaveBeenCalledWith('https://captcha.website/'); + }); + + test('[workaround] invalid request: with a valid referer header', async () => { + const storage = new StorageMock(); + const updateIcon = jest.fn(); + const navigateUrl = jest.fn(); + + const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); + const tokens = [new Token(), new Token(), new Token()]; + const issue = jest.fn(async () => { + return tokens; + }); + provider['issue'] = issue; + let result, issueInfo; + + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = { + ...validDetails, + url: validDetails.url.substring(0, validDetails.url.indexOf('?')), + }; + result = provider.handleBeforeRequest(bodyDetails); + expect(result).toEqual({ cancel: false }); + + // Expect issueInfo to be set. + issueInfo = provider['issueInfo']; + expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); + expect(issueInfo!.url).toEqual(bodyDetails.url); + expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + + const headDetails: any = { + ...validDetails, + requestHeaders: [ + { name: "referer", value: validDetails.url.replace('__cf_chl_captcha_tk__', '__cf_chl_tk') }, + ], + }; + delete headDetails.requestBody; - const result = provider.handleBeforeRequest(validDetails); + result = provider.handleBeforeSendHeaders(headDetails); expect(result).toEqual({ cancel: true }); + // Expect issueInfo to be unset. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 0); jest.runAllTimers(); await Promise.resolve(); expect(issue.mock.calls.length).toBe(1); - expect(issue).toHaveBeenCalledWith(validDetails.url, { + expect(issue).toHaveBeenCalledWith(headDetails.url, { 'h-captcha-response': 'body-param', }); @@ -160,7 +249,8 @@ describe('issuance', () => { * b. 'h-captcha-response' * c. 'cf_captcha_kind' */ - test('invalid request w/ no query param', async () => { + + test('invalid request: no query param', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -168,23 +258,41 @@ describe('issuance', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const issue = jest.fn(async () => []); provider['issue'] = issue; + let result, issueInfo; - const details = { + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, url: validDetails.url.substring(0, validDetails.url.indexOf('?')), }; - const result = provider.handleBeforeRequest(details); + result = provider.handleBeforeRequest(bodyDetails); + expect(result).toEqual({ cancel: false }); + + // Expect issueInfo to be set. + issueInfo = provider['issueInfo']; + expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); + expect(issueInfo!.url).toEqual(bodyDetails.url); + expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + + const headDetails: any = { + ...validDetails, + requestHeaders: [] + }; + delete headDetails.requestBody; + + result = provider.handleBeforeSendHeaders(headDetails); expect(result).toBeUndefined(); - expect(setTimeout).toHaveBeenCalledTimes(0); - await Promise.resolve(); + // Expect issueInfo to be unset. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + expect(setTimeout).not.toHaveBeenCalled(); expect(issue).not.toHaveBeenCalled(); expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); }); - test('invalid request w/ no body param', async () => { + test('invalid request: no body param', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -192,17 +300,33 @@ describe('issuance', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); const issue = jest.fn(async () => []); provider['issue'] = issue; + let result, issueInfo; - const details = { + const bodyDetails: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, requestBody: {}, }; - const result = provider.handleBeforeRequest(details); + result = provider.handleBeforeRequest(bodyDetails); expect(result).toBeUndefined(); - expect(setTimeout).toHaveBeenCalledTimes(0); - await Promise.resolve(); + // Expect issueInfo to be unset. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + + const headDetails: any = { + ...validDetails, + requestHeaders: [] + }; + delete headDetails.requestBody; + + result = provider.handleBeforeSendHeaders(headDetails); + expect(result).toBeUndefined(); + // Expect issueInfo to be unset. + issueInfo = provider['issueInfo']; + expect(issueInfo).toBeNull(); + + expect(setTimeout).not.toHaveBeenCalled(); expect(issue).not.toHaveBeenCalled(); expect(updateIcon).not.toHaveBeenCalled(); expect(navigateUrl).not.toHaveBeenCalled(); @@ -211,32 +335,23 @@ describe('issuance', () => { }); /* - * The redemption involves handleHeadersReceived and handleBeforeSendHeaders - * listeners. In handleHeadersReceived listener, - * 1. Firstly, the listener check if the response is the challenge page and - * it supports Privacy Pass redemption. - * 2. If it passes the check, the listener gets a token from the storage to - * redeem. - * 3. The listener sets "redeemInfo" property which includes the request id - * and the mentioned token. The property will be used by - * handleBeforeSendHeaders to redeem the token. - * 4. The listener returns the redirect command so that the browser will - * send the same request again with the token attached. + * The redemption involves handleHeadersReceived and handleBeforeSendHeaders listeners. + * + * In handleHeadersReceived listener, + * 1. Check that the request matches the criteria for redemption. + * Such requests have a status code of 403, and a special header. + * 2. If so, the listener sets the "redeemInfo" property, + * and returns a value that causes the request to be resent. * * In handleBeforeSendHeaders, - * 1. The listener will check if the provided request id matches the - * request id in "redeemInfo". If so, it means that the request is from the - * redirect command returned by handleHeadersReceived. If not, it returns - * nothing and let the request continue. - * 2. If it passes the check, the listener attaches the token from - * "redeemInfo" in the "challenge-bypass-token" HTTP header and clears the - * "redeemInfo" property because "redeemInfo" is used already. + * 1. Check that the "redeemInfo" property is set, and its request id is a match. + * 2. If so, add headers to include one token for redemption by provider. */ describe('redemption', () => { describe('handleHeadersReceived', () => { - const validDetails = { + const validDetails: chrome.webRequest.WebResponseHeadersDetails = { method: 'GET', - url: 'https://cloudflare.com/', + url: 'https://non-issuing-domain.example.com/', requestId: 'xxx', frameId: 1, parentFrameId: 1, @@ -253,7 +368,7 @@ describe('redemption', () => { ], }; - test('valid response with tokens', () => { + test('valid response with tokens in wallet', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -265,7 +380,7 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = validDetails; + const details: chrome.webRequest.WebResponseHeadersDetails = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toEqual({ redirectUrl: details.url }); @@ -287,7 +402,7 @@ describe('redemption', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - test('valid response without tokens', () => { + test('valid response without tokens in wallet', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -298,7 +413,7 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith('0'); - const details = validDetails; + const details: chrome.webRequest.WebResponseHeadersDetails = validDetails; const result = provider.handleHeadersReceived(details); expect(result).toBeUndefined(); @@ -307,7 +422,14 @@ describe('redemption', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - test('no response from an issuing domain', () => { + /* + * The response is invalid if any of the followings is true: + * 1. The URL is hosted by a site that issues tokens. + * 2. The status code is not 403. + * 3. There is no HTTP header of "cf-chl-bypass: 1" + */ + + test('invalid response: from issuing domain', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -319,7 +441,7 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebResponseHeadersDetails = { ...validDetails, url: 'https://captcha.website/', }; @@ -330,12 +452,7 @@ describe('redemption', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - /* - * The response is invalid if any of the followings is true: - * 1. The status code is not 403. - * 2. There is no HTTP header of "cf-chl-bypass: 1" - */ - test('invalid response w/ wrong status code', () => { + test('invalid response: wrong status code', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -347,7 +464,7 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebResponseHeadersDetails = { ...validDetails, statusLine: 'HTTP/1.1 200 OK', statusCode: 200, @@ -359,7 +476,7 @@ describe('redemption', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - test('invalid response w/ no bypass header', () => { + test('invalid response: no bypass header', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -371,7 +488,7 @@ describe('redemption', () => { expect(updateIcon.mock.calls.length).toBe(1); expect(updateIcon).toHaveBeenCalledWith(tokens.length.toString()); - const details = { + const details: chrome.webRequest.WebResponseHeadersDetails = { ...validDetails, responseHeaders: [], }; @@ -384,9 +501,9 @@ describe('redemption', () => { }); describe('handleBeforeSendHeaders', () => { - const validDetails = { + const validDetails: chrome.webRequest.WebRequestHeadersDetails = { method: 'GET', - url: 'https://cloudflare.com/', + url: 'https://non-issuing-domain.example.com/', requestId: 'xxx', frameId: 1, parentFrameId: 1, @@ -396,10 +513,6 @@ describe('redemption', () => { requestHeaders: [], }; - afterEach(() => { - validDetails.requestHeaders = []; - }); - test('with redeemInfo', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); @@ -416,13 +529,13 @@ describe('redemption', () => { }; provider['redeemInfo'] = redeemInfo; - const details = validDetails; + const details: chrome.webRequest.WebRequestHeadersDetails = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toEqual({ requestHeaders: [ { name: 'challenge-bypass-token', - value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJyeXRSRExLN3J2THVhd09XZkJ0RXJTclVuUWpIaGpLbkNKK3RqQnhQSFYwPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', + value: 'eyJ0eXBlIjoiUmVkZWVtIiwiY29udGVudHMiOlsiN3Mweit1TDdrRVNxUk9zWjU1aDlQOWNLS2lWQm5UZ1dZaGVCQ1oyejMwQT0iLCJIWVA2QnlqYmFCK0trNG9qM2Rtazc4Qy9aWWFMVlNYTHZtT0JIMms0QTFRPSIsImV5SmpkWEoyWlNJNkluQXlOVFlpTENKb1lYTm9Jam9pYzJoaE1qVTJJaXdpYldWMGFHOWtJam9pYVc1amNtVnRaVzUwSW4wPSJdfQ==', }, ], }); @@ -441,7 +554,7 @@ describe('redemption', () => { const provider = new CloudflareProvider(storage, { updateIcon, navigateUrl }); - const details = validDetails; + const details: chrome.webRequest.WebRequestHeadersDetails = validDetails; const result = provider.handleBeforeSendHeaders(details); expect(result).toBeUndefined(); diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index a3e97207..0e80b276 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -40,6 +40,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExf0AftemLr0YSz5odoj3eJv6SkOF VcH7NNb2xwdEz6Pxm44tvovEl/E+si8hdIDVg1Ys+cbaWwP0jYJW3ygv+Q== -----END PUBLIC KEY-----`; +interface IssueInfo { + requestId: string; + url: string; + formData: { [key: string]: string[] | string }; +} + interface RedeemInfo { requestId: string; token: Token; @@ -57,6 +63,7 @@ export class CloudflareProvider extends Provider { private VOPRF: voprf.VOPRF; private callbacks: Callbacks; private storage: Storage; + private issueInfo: IssueInfo | null; private redeemInfo: RedeemInfo | null; constructor(storage: Storage, callbacks: Callbacks) { @@ -65,6 +72,7 @@ export class CloudflareProvider extends Provider { this.VOPRF = new voprf.VOPRF(voprf.defaultECSettings); this.callbacks = callbacks; this.storage = storage; + this.issueInfo = null; this.redeemInfo = null; } @@ -108,20 +116,16 @@ export class CloudflareProvider extends Provider { : {} ; - if (this.matchesIssuingCriteria(details, url, formData)) { - setTimeout( - (): void => { - this.sendIssueRequest(details.url, formData); - }, - 0 - ); + if (this.matchesIssuingBodyCriteria(details, url, formData)) { + this.issueInfo = { requestId: details.requestId, url: details.url, formData }; - // safe to cancel - return { cancel: true }; + // do NOT cancel the request with captcha solution. + // note: "handleBeforeSendHeaders" will cancel this request if additional criteria are satisfied. + return { cancel: false }; } } - private matchesIssuingCriteria( + private matchesIssuingBodyCriteria( details: chrome.webRequest.WebRequestBodyDetails, url: URL, formData: { [key: string]: string[] | string }, @@ -145,11 +149,6 @@ export class CloudflareProvider extends Provider { return false; } - // Only issue tokens when querystring parameters pass defined criteria. - if (!areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { - return false; - } - // Only issue tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { return false; @@ -158,6 +157,144 @@ export class CloudflareProvider extends Provider { return true; } + handleHeadersReceived( + details: chrome.webRequest.WebResponseHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + // Don't redeem a token on the issuing website. + if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { + return; + } + + // Check if it's the response of the request that we should insert a token. + if (details.statusCode !== 403 || details.responseHeaders === undefined) { + return; + } + const hasSupportHeader = details.responseHeaders.some((header) => { + return ( + header.name.toLowerCase() === CHL_BYPASS_SUPPORT && + header.value !== undefined && + parseInt(header.value, 10) === CloudflareProvider.ID + ); + }); + if (!hasSupportHeader) { + return; + } + + // Redeem one token (if available) + + const tokens = this.getStoredTokens(); + const token = tokens.shift(); + this.setStoredTokens(tokens); + + // No tokens in wallet! + if (token === undefined) { + return; + } + + this.redeemInfo = { requestId: details.requestId, token }; + + // Redirect to resend the request attached with the token. + return { + redirectUrl: details.url, + }; + } + + handleBeforeSendHeaders( + details: chrome.webRequest.WebRequestHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + + if (this.issueInfo !== null) { + if (this.matchesIssuingHeadersCriteria(details)) { + const issueInfo: IssueInfo = { ...this.issueInfo }; + + // Clear the issue info. + this.issueInfo = null; + + setTimeout( + (): void => { + this.sendIssueRequest(issueInfo.url, issueInfo.formData); + }, + 0 + ); + + // cancel the request with captcha solution. + return { cancel: true }; + } + else { + // Clear the issue info. + this.issueInfo = null; + } + } + + return this.redeemToken(details); + } + + private redeemToken( + details: chrome.webRequest.WebRequestHeadersDetails, + ): chrome.webRequest.BlockingResponse | void { + if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { + return; + } + + const token = this.redeemInfo!.token; + + // Clear the redeem info. + this.redeemInfo = null; + + const url = new URL(details.url); + const key = token.getMacKey(); + const binding = this.VOPRF.createRequestBinding(key, [ + voprf.getBytesFromString(url.hostname), + voprf.getBytesFromString(details.method + ' ' + url.pathname), + ]); + + const contents = [ + voprf.getBase64FromBytes(token.getInput()), + binding, + voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), + ]; + const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); + + const headers = details.requestHeaders ?? []; + headers.push({ name: 'challenge-bypass-token', value: redemption }); + + return {requestHeaders: headers}; + } + + private matchesIssuingHeadersCriteria( + details: chrome.webRequest.WebRequestHeadersDetails, + ): boolean { + let href: string; + let url: URL; + + if (this.issueInfo === null) return false; + + href = this.issueInfo.url; + url = new URL(href); + + // Only issue tokens when querystring parameters pass defined criteria. + if (areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + return true; + } + + if (details.requestHeaders) { + const ref_header = details.requestHeaders.find(h => (h !== undefined) && h.name && h.value && (h.name.toLowerCase() === 'referer')); + + if (ref_header !== undefined) { + href = ref_header.value!.replace(/([\?&])(?:__cf_chl_tk)([=])/ig, ('$1' + '__cf_chl_captcha_tk__' + '$2')); + url = new URL(href); + + // Only issue tokens when querystring parameters pass defined criteria. + if (areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + this.issueInfo.url = href; + return true; + } + } + } + + return false; + } + private async sendIssueRequest( url: string, formData: { [key: string]: string[] | string }, @@ -306,80 +443,6 @@ export class CloudflareProvider extends Provider { return item; } - handleHeadersReceived( - details: chrome.webRequest.WebResponseHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { - // Don't redeem a token on the issuing website. - if (isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, new URL(details.url))) { - return; - } - - // Check if it's the response of the request that we should insert a token. - if (details.statusCode !== 403 || details.responseHeaders === undefined) { - return; - } - const hasSupportHeader = details.responseHeaders.some((header) => { - return ( - header.name.toLowerCase() === CHL_BYPASS_SUPPORT && - header.value !== undefined && - parseInt(header.value, 10) === CloudflareProvider.ID - ); - }); - if (!hasSupportHeader) { - return; - } - - // Redeem one token (if available) - - const tokens = this.getStoredTokens(); - const token = tokens.shift(); - this.setStoredTokens(tokens); - - // No tokens in wallet! - if (token === undefined) { - return; - } - - this.redeemInfo = { requestId: details.requestId, token }; - - // Redirect to resend the request attached with the token. - return { - redirectUrl: details.url, - }; - } - - handleBeforeSendHeaders( - details: chrome.webRequest.WebRequestHeadersDetails, - ): chrome.webRequest.BlockingResponse | void { - if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { - return; - } - - const token = this.redeemInfo!.token; - - // Clear the redeem info. - this.redeemInfo = null; - - const url = new URL(details.url); - const key = token.getMacKey(); - const binding = this.VOPRF.createRequestBinding(key, [ - voprf.getBytesFromString(url.hostname), - voprf.getBytesFromString(details.method + ' ' + url.pathname), - ]); - - const contents = [ - voprf.getBase64FromBytes(token.getInput()), - binding, - voprf.getBase64FromString(JSON.stringify(voprf.defaultECSettings)), - ]; - const redemption = btoa(JSON.stringify({ type: 'Redeem', contents })); - - const headers = details.requestHeaders ?? []; - headers.push({ name: 'challenge-bypass-token', value: redemption }); - - return {requestHeaders: headers}; - } - handleOnCompleted( _details: chrome.webRequest.WebResponseHeadersDetails, ): void { diff --git a/src/background/providers/hcaptcha.test.ts b/src/background/providers/hcaptcha.test.ts index d1b27dd8..7232d1ab 100644 --- a/src/background/providers/hcaptcha.test.ts +++ b/src/background/providers/hcaptcha.test.ts @@ -74,7 +74,7 @@ test('getBadgeText', () => { * The issuance involves handleBeforeRequest and handleOnCompleted listeners. * * In handleBeforeRequest listener, - * 1. Check that the request matches the criteria for redemption. + * 1. Check that the request matches the criteria for issuing new tokens. * Such requests are submitting a solved captcha to the provider * on a website controlled by the provider. * 2. If so, the listener sets the "issueInfo" property, @@ -158,7 +158,8 @@ describe('issuance', () => { * 2. Its pathname does not contain of any of the followings: * a. '/checkcaptcha' */ - test('invalid request w/ no query param', async () => { + + test('invalid request: no query param', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -180,7 +181,7 @@ describe('issuance', () => { expect(navigateUrl).not.toHaveBeenCalled(); }); - test('invalid request w/ no matching pathname', async () => { + test('invalid request: no matching pathname', () => { const storage = new StorageMock(); const updateIcon = jest.fn(); const navigateUrl = jest.fn(); @@ -263,7 +264,7 @@ describe('redemption', () => { const headDetails: any = { ...validDetails, - requestHeaders: [] + requestHeaders: [], }; delete headDetails.requestBody; @@ -308,7 +309,7 @@ describe('redemption', () => { const headDetails: any = { ...validDetails, - requestHeaders: [] + requestHeaders: [], }; delete headDetails.requestBody; @@ -337,9 +338,9 @@ describe('redemption', () => { requestBody: { formData: { ...validDetails.requestBody!.formData!, - host: ['www.hcaptcha.com'] - } - } + host: ['www.hcaptcha.com'], + }, + }, }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); @@ -365,9 +366,9 @@ describe('redemption', () => { requestBody: { formData: { ...validDetails.requestBody!.formData!, - sitekey: ['00000000-0000-0000-0000-000000000000'] - } - } + sitekey: ['00000000-0000-0000-0000-000000000000'], + }, + }, }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); @@ -390,7 +391,7 @@ describe('redemption', () => { const details: chrome.webRequest.WebRequestBodyDetails = { ...validDetails, - url: validDetails.url.replace(/\?s=.*$/, '?s=00000000-0000-0000-0000-000000000000') + url: validDetails.url.replace(/\?s=.*$/, '?s=00000000-0000-0000-0000-000000000000'), }; const result = provider.handleBeforeRequest(details); expect(result).toBeUndefined(); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index c2048a75..7c4c8cbe 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -269,9 +269,7 @@ export class HcaptchaProvider extends Provider { { name: 'challenge-bypass-token', value: redemption }, ); - return { - requestHeaders: headers, - }; + return {requestHeaders: headers}; } handleHeadersReceived( @@ -298,17 +296,22 @@ export class HcaptchaProvider extends Provider { (this.issueInfo !== null) && (this.issueInfo.requestId === requestId) ) { - const url: string = this.issueInfo!.url; + try { + const url: string = this.issueInfo!.url; - // Clear the issue info. - this.issueInfo = null; + // Clear the issue info. + this.issueInfo = null; - // Issue tokens. - const tokens = await this.issue(url); + // Issue tokens. + const tokens = await this.issue(url); - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + } + catch(error: any) { + console.error(error.message); + } this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); } From 3e39527f7952bb6468be8860d177021ff30229ab Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 29 Jan 2022 02:50:01 -0800 Subject: [PATCH 28/36] fix: use feature detection so browser compatability isn't reduced notes: ====== * only Chrome 72+ recognize the "extraHeaders" option value - both Firefox and older versions of Chrome will throw an error if this value is included when adding a listener to a "chrome.webRequest" event annoyances: =========== * the typescript definitions got in the way * "@types/chrome" doesn't define types for any of the "On---Options" classes in "chrome.webRequest" * for this reason, any time they need to be accessed.. "chrome.webRequest" needs to be cast to "any" * a future version of "@types/chrome" will hopefully allow this casting to be removed observations regarding status of browser compatability: ======================================================= * tested in Chrome 85 - Cloudflare * working: - issuing of tokens - redeeming of tokens - hCaptcha * working: - issuing of tokens - redeeming of tokens * tested in Chrome 30 - Cloudflare * not working: - issuing of tokens - hCaptcha * working: - issuing of tokens - redeeming of tokens * note: uses 2x tokens per bypassed captcha * tested in Firefox 97 - Cloudflare * working: - issuing of tokens - redeeming of tokens - hCaptcha * not working: - issuing of tokens to do: ====== * investigate why hCaptcha isn't issuing tokens in Firefox - it's behaving as though the original request had been cancelled - the request to issue tokens is being correctly sent to the provider, but it's being returned with a 403 status code: {"success":false,"error-codes":["invalid-data"]} --- package.json | 2 +- public/manifest.json | 2 +- src/background/index.ts | 60 ++++++++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 75a2401b..47cfc34d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.2", + "version": "3.6.3", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 6937e0bf..a7bb47b9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.2", + "version": "3.6.3", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/index.ts b/src/background/index.ts index 898ca2c5..8fe80e5b 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -72,30 +72,42 @@ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs: chrome. } }); -chrome.webRequest.onBeforeRequest.addListener(handleBeforeRequest, { urls: [''] }, [ - 'blocking', - 'requestBody', - 'extraHeaders', -]); - -chrome.webRequest.onBeforeSendHeaders.addListener(handleBeforeSendHeaders, { urls: [''] }, [ - 'blocking', - 'requestHeaders', - 'extraHeaders', -]); - -chrome.webRequest.onHeadersReceived.addListener(handleHeadersReceived, { urls: [''] }, [ - 'blocking', - 'responseHeaders', - 'extraHeaders', -]); - -chrome.webRequest.onCompleted.addListener(handleOnCompleted, { urls: [''] }, [ - 'responseHeaders', - 'extraHeaders', -]); - -chrome.webRequest.onErrorOccurred.addListener(handleOnErrorOccurred, { urls: [''] }); +chrome.webRequest.onBeforeRequest.addListener( + handleBeforeRequest, + { urls: [''] }, + ((chrome.webRequest).OnBeforeRequestOptions !== undefined) + ? Object.values((chrome.webRequest).OnBeforeRequestOptions) + : ['blocking', 'requestBody'] +); + +chrome.webRequest.onBeforeSendHeaders.addListener( + handleBeforeSendHeaders, + { urls: [''] }, + ((chrome.webRequest).OnBeforeSendHeadersOptions !== undefined) + ? Object.values((chrome.webRequest).OnBeforeSendHeadersOptions) + : ['blocking', 'requestHeaders'] +); + +chrome.webRequest.onHeadersReceived.addListener( + handleHeadersReceived, + { urls: [''] }, + ((chrome.webRequest).OnHeadersReceivedOptions !== undefined) + ? Object.values((chrome.webRequest).OnHeadersReceivedOptions) + : ['blocking', 'responseHeaders'] +); + +chrome.webRequest.onCompleted.addListener( + handleOnCompleted, + { urls: [''] }, + ((chrome.webRequest).OnCompletedOptions !== undefined) + ? Object.values((chrome.webRequest).OnCompletedOptions) + : ['responseHeaders'] +); + +chrome.webRequest.onErrorOccurred.addListener( + handleOnErrorOccurred, + { urls: [''] }, +); chrome.cookies.onChanged.addListener(handleChangedCookies); From 84ea20bb4c964b63f330c9a0090355a191e38594 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 29 Jan 2022 22:11:24 -0800 Subject: [PATCH 29/36] fix a conditional statement that may effect Cloudflare token issuing --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.ts | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 47cfc34d..22f14254 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.3", + "version": "3.6.4", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index a7bb47b9..5068c99a 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.3", + "version": "3.6.4", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 0e80b276..4424162a 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -203,7 +203,10 @@ export class CloudflareProvider extends Provider { details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - if (this.issueInfo !== null) { + if ( + (this.issueInfo !== null) && + (this.issueInfo.requestId === details.requestId) + ) { if (this.matchesIssuingHeadersCriteria(details)) { const issueInfo: IssueInfo = { ...this.issueInfo }; @@ -232,7 +235,10 @@ export class CloudflareProvider extends Provider { private redeemToken( details: chrome.webRequest.WebRequestHeadersDetails, ): chrome.webRequest.BlockingResponse | void { - if (this.redeemInfo === null || details.requestId !== this.redeemInfo.requestId) { + if ( + (this.redeemInfo === null) || + (this.redeemInfo.requestId !== details.requestId) + ) { return; } @@ -267,7 +273,12 @@ export class CloudflareProvider extends Provider { let href: string; let url: URL; - if (this.issueInfo === null) return false; + if ( + (this.issueInfo === null) || + (this.issueInfo.requestId !== details.requestId) + ) { + return false; + } href = this.issueInfo.url; url = new URL(href); From 29febe8a8b83abdef7f460b512035b74e7f5ddf1 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sat, 29 Jan 2022 23:20:23 -0800 Subject: [PATCH 30/36] minor refactoring --- src/background/providers/cloudflare.ts | 32 ++++++++++++++------- src/background/providers/hcaptcha.ts | 40 ++++++++++++++------------ 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 4424162a..a7fdcdfc 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -208,17 +208,7 @@ export class CloudflareProvider extends Provider { (this.issueInfo.requestId === details.requestId) ) { if (this.matchesIssuingHeadersCriteria(details)) { - const issueInfo: IssueInfo = { ...this.issueInfo }; - - // Clear the issue info. - this.issueInfo = null; - - setTimeout( - (): void => { - this.sendIssueRequest(issueInfo.url, issueInfo.formData); - }, - 0 - ); + this.triggerIssueRequest(details.requestId); // cancel the request with captcha solution. return { cancel: true }; @@ -306,6 +296,26 @@ export class CloudflareProvider extends Provider { return false; } + private triggerIssueRequest(requestId: string): void { + // Is the current (cancelled) request a trigger to initiate a secondary request to the provider for the issuing of signed tokens? + if ( + (this.issueInfo !== null) && + (this.issueInfo.requestId === requestId) + ) { + const issueInfo: IssueInfo = { ...this.issueInfo }; + + // Clear the issue info. + this.issueInfo = null; + + setTimeout( + (): void => { + this.sendIssueRequest(issueInfo.url, issueInfo.formData); + }, + 0 + ); + } + } + private async sendIssueRequest( url: string, formData: { [key: string]: string[] | string }, diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 7c4c8cbe..9d5e96ba 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -281,40 +281,44 @@ export class HcaptchaProvider extends Provider { handleOnCompleted( details: chrome.webRequest.WebResponseHeadersDetails, ): void { - this.sendIssueRequest(details.requestId); + this.triggerIssueRequest(details.requestId); } handleOnErrorOccurred( details: chrome.webRequest.WebResponseErrorDetails, ): void { - this.sendIssueRequest(details.requestId); + this.triggerIssueRequest(details.requestId); } - private async sendIssueRequest(requestId: string): Promise { - // Is the completed request a trigger to initiate a secondary request to the provider for the issuing of signed tokens? + private triggerIssueRequest(requestId: string): void { + // Is the current (completed) request a trigger to initiate a secondary request to the provider for the issuing of signed tokens? if ( (this.issueInfo !== null) && (this.issueInfo.requestId === requestId) ) { - try { - const url: string = this.issueInfo!.url; + const url: string = this.issueInfo!.url; - // Clear the issue info. - this.issueInfo = null; + // Clear the issue info. + this.issueInfo = null; - // Issue tokens. - const tokens = await this.issue(url); + this.sendIssueRequest(url); + } + } - // Store tokens. - const cached = this.getStoredTokens(); - this.setStoredTokens(cached.concat(tokens)); - } - catch(error: any) { - console.error(error.message); - } + private async sendIssueRequest(url: string): Promise { + try { + // Issue tokens. + const tokens = await this.issue(url); - this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); + // Store tokens. + const cached = this.getStoredTokens(); + this.setStoredTokens(cached.concat(tokens)); + } + catch(error: any) { + console.error(error.message); } + + this.callbacks.navigateUrl(HcaptchaProvider.EARNED_TOKEN_COOKIE.url); } private async issue(url: string): Promise { From 8283f46ae8b0aa04be0dfc038c397b8c38e5babd Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Mon, 31 Jan 2022 01:33:24 -0800 Subject: [PATCH 31/36] add static helper method: "getNormalizedFormData" During some real-world testing, I noticed that Chrome doesn't always parse 'application/x-www-form-urlencoded' data in the POST body. This new helper method is available to providers to normalize the format of this data into a key/value hash object. Furthermore, it will also parse 'application/json' data. --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.test.ts | 10 ++- src/background/providers/cloudflare.ts | 52 +++++-------- src/background/providers/hcaptcha.ts | 82 +++++++++++---------- src/background/providers/provider.ts | 59 +++++++++++++++ 6 files changed, 132 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 22f14254..b9f02b7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.4", + "version": "3.6.5", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 5068c99a..ed8bcc2c 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.4", + "version": "3.6.5", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 6a0d75b9..177e9e26 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -116,6 +116,10 @@ describe('issuance', () => { }, }; + const flattenFormData: { [key: string]: string[] | string } = { + 'h-captcha-response': 'body-param', + }; + test('valid request', async () => { const storage = new StorageMock(); const updateIcon = jest.fn(); @@ -137,7 +141,7 @@ describe('issuance', () => { issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); expect(issueInfo!.url).toEqual(bodyDetails.url); - expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + expect(issueInfo!.formData).toEqual(flattenFormData); const headDetails: any = { ...validDetails, @@ -199,7 +203,7 @@ describe('issuance', () => { issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); expect(issueInfo!.url).toEqual(bodyDetails.url); - expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + expect(issueInfo!.formData).toEqual(flattenFormData); const headDetails: any = { ...validDetails, @@ -271,7 +275,7 @@ describe('issuance', () => { issueInfo = provider['issueInfo']; expect(issueInfo!.requestId).toEqual(bodyDetails.requestId); expect(issueInfo!.url).toEqual(bodyDetails.url); - expect(issueInfo!.formData).toEqual(bodyDetails.requestBody!.formData); + expect(issueInfo!.formData).toEqual(flattenFormData); const headDetails: any = { ...validDetails, diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index a7fdcdfc..7559a41e 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -1,6 +1,6 @@ import * as voprf from '../crypto/voprf'; -import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams } from './provider'; +import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams, getNormalizedFormData } from './provider'; import { Storage } from '../storage'; import Token from '../token'; import axios from 'axios'; @@ -16,23 +16,24 @@ const COMMITMENT_URL: string = 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; const ALL_ISSUING_CRITERIA: { - HOSTNAMES: QUALIFIED_HOSTNAMES; - PATHNAMES: QUALIFIED_PATHNAMES; - QUERY_PARAMS: QUALIFIED_PARAMS; - BODY_PARAMS: QUALIFIED_PARAMS; + HOSTNAMES: QUALIFIED_HOSTNAMES | void; + PATHNAMES: QUALIFIED_PATHNAMES | void; + QUERY_PARAMS: QUALIFIED_PARAMS | void; + BODY_PARAMS: QUALIFIED_PARAMS | void; } = { HOSTNAMES: { exact : [DEFAULT_ISSUING_HOSTNAME], contains: [`.${DEFAULT_ISSUING_HOSTNAME}`], }, PATHNAMES: { + exact : ['/'], }, QUERY_PARAMS: { - some: ['__cf_chl_captcha_tk__', '__cf_chl_managed_tk__'], + some: ['__cf_chl_captcha_tk__', '__cf_chl_managed_tk__'], }, BODY_PARAMS: { - some: ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind'], - } + some: ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind'], + }, } const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- @@ -110,15 +111,8 @@ export class CloudflareProvider extends Provider { handleBeforeRequest( details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) - ? details.requestBody.formData - : {} - ; - - if (this.matchesIssuingBodyCriteria(details, url, formData)) { - this.issueInfo = { requestId: details.requestId, url: details.url, formData }; + if (this.matchesIssuingBodyCriteria(details)) { // do NOT cancel the request with captcha solution. // note: "handleBeforeSendHeaders" will cancel this request if additional criteria are satisfied. return { cancel: false }; @@ -126,10 +120,9 @@ export class CloudflareProvider extends Provider { } private matchesIssuingBodyCriteria( - details: chrome.webRequest.WebRequestBodyDetails, - url: URL, - formData: { [key: string]: string[] | string }, + details: chrome.webRequest.WebRequestBodyDetails, ): boolean { + // Only issue tokens for POST requests that contain data in body. if ( (details.method.toUpperCase() !== 'POST' ) || @@ -139,6 +132,8 @@ export class CloudflareProvider extends Provider { return false; } + const url: URL = new URL(details.url); + // Only issue tokens to hosts belonging to the provider. if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { return false; @@ -149,11 +144,15 @@ export class CloudflareProvider extends Provider { return false; } - // Only issue tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. + const formData: { [key: string]: string[] | string } = getNormalizedFormData(details, /* flatten= */ true); + + // Only issue tokens when 'application/x-www-form-urlencoded' or 'application/json' data parameters in POST body pass defined criteria. if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { return false; } + this.issueInfo = { requestId: details.requestId, url: details.url, formData }; + return true; } @@ -321,19 +320,8 @@ export class CloudflareProvider extends Provider { formData: { [key: string]: string[] | string }, ): Promise { try { - // Normalize 'application/x-www-form-urlencoded' data parameters in POST body - const flattenFormData: { [key: string]: string[] | string } = {}; - for (const key in formData) { - if (Array.isArray(formData[key]) && (formData[key].length === 1)) { - const [value] = formData[key]; - flattenFormData[key] = value; - } else { - flattenFormData[key] = formData[key]; - } - } - // Issue tokens. - const tokens = await this.issue(url, flattenFormData); + const tokens = await this.issue(url, formData); // Store tokens. const cached = this.getStoredTokens(); diff --git a/src/background/providers/hcaptcha.ts b/src/background/providers/hcaptcha.ts index 9d5e96ba..3cef9745 100644 --- a/src/background/providers/hcaptcha.ts +++ b/src/background/providers/hcaptcha.ts @@ -1,6 +1,6 @@ import * as voprf from '../crypto/voprf'; -import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams } from './provider'; +import { Provider, EarnedTokenCookie, Callbacks, QUALIFIED_HOSTNAMES, QUALIFIED_PATHNAMES, QUALIFIED_PARAMS, isIssuingHostname, isQualifiedPathname, areQualifiedQueryParams, areQualifiedBodyFormParams, getNormalizedFormData } from './provider'; import { Storage } from '../storage'; import Token from '../token'; import axios from 'axios'; @@ -15,10 +15,10 @@ const COMMITMENT_URL: string = 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; const ALL_ISSUING_CRITERIA: { - HOSTNAMES: QUALIFIED_HOSTNAMES; - PATHNAMES: QUALIFIED_PATHNAMES; - QUERY_PARAMS: QUALIFIED_PARAMS; - BODY_PARAMS: QUALIFIED_PARAMS; + HOSTNAMES: QUALIFIED_HOSTNAMES | void; + PATHNAMES: QUALIFIED_PATHNAMES | void; + QUERY_PARAMS: QUALIFIED_PARAMS | void; + BODY_PARAMS: QUALIFIED_PARAMS | void; } = { HOSTNAMES: { exact : [DEFAULT_ISSUING_HOSTNAME], @@ -28,17 +28,16 @@ const ALL_ISSUING_CRITERIA: { contains: ['/checkcaptcha'], }, QUERY_PARAMS: { - some: ['s=00000000-0000-0000-0000-000000000000'], + some: ['s=00000000-0000-0000-0000-000000000000'], }, - BODY_PARAMS: { - } + BODY_PARAMS: undefined, } const ALL_REDEMPTION_CRITERIA: { - HOSTNAMES: QUALIFIED_HOSTNAMES; - PATHNAMES: QUALIFIED_PATHNAMES; - QUERY_PARAMS: QUALIFIED_PARAMS; - BODY_PARAMS: QUALIFIED_PARAMS; + HOSTNAMES: QUALIFIED_HOSTNAMES | void; + PATHNAMES: QUALIFIED_PATHNAMES | void; + QUERY_PARAMS: QUALIFIED_PARAMS | void; + BODY_PARAMS: QUALIFIED_PARAMS | void; } = { HOSTNAMES: { exact : [DEFAULT_ISSUING_HOSTNAME], @@ -48,11 +47,11 @@ const ALL_REDEMPTION_CRITERIA: { contains: ['/getcaptcha'], }, QUERY_PARAMS: { - some: ['s!=00000000-0000-0000-0000-000000000000'], + some: ['s!=00000000-0000-0000-0000-000000000000'], }, BODY_PARAMS: { - every: ['sitekey!=00000000-0000-0000-0000-000000000000', 'motionData', 'host!=www.hcaptcha.com'], - } + every: ['sitekey!=00000000-0000-0000-0000-000000000000', 'motionData', 'host!=www.hcaptcha.com'], + }, } const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- @@ -128,22 +127,13 @@ export class HcaptchaProvider extends Provider { handleBeforeRequest( details: chrome.webRequest.WebRequestBodyDetails, ): chrome.webRequest.BlockingResponse | void { - const url = new URL(details.url); - const formData: { [key: string]: string[] | string } = (details.requestBody && details.requestBody.formData) - ? details.requestBody.formData - : {} - ; - - if (this.matchesIssuingCriteria(details, url, formData)) { - this.issueInfo = { requestId: details.requestId, url: details.url }; + if (this.matchesIssuingCriteria(details)) { // do NOT cancel the request with captcha solution. return { cancel: false }; } - if (this.matchesRedemptionCriteria(details, url, formData)) { - this.redeemInfo = { requestId: details.requestId }; - + if (this.matchesRedemptionCriteria(details)) { // do NOT cancel the request to generate a new captcha. // note: "handleBeforeSendHeaders" will add request headers to embed a token. return { cancel: false }; @@ -151,10 +141,9 @@ export class HcaptchaProvider extends Provider { } private matchesIssuingCriteria( - details: chrome.webRequest.WebRequestBodyDetails, - url: URL, - formData: { [key: string]: string[] | string } + details: chrome.webRequest.WebRequestBodyDetails, ): boolean { + // Only issue tokens for POST requests that contain data in body. if ( (details.method.toUpperCase() !== 'POST' ) || @@ -164,6 +153,8 @@ export class HcaptchaProvider extends Provider { return false; } + const url: URL = new URL(details.url); + // Only issue tokens to hosts belonging to the provider. if (!isIssuingHostname(ALL_ISSUING_CRITERIA.HOSTNAMES, url)) { return false; @@ -179,19 +170,25 @@ export class HcaptchaProvider extends Provider { return false; } - // Only issue tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. - if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { - return false; + // conditionally short-circuit an expensive operation + if (ALL_ISSUING_CRITERIA.BODY_PARAMS !== undefined) { + const formData: { [key: string]: string[] | string } = getNormalizedFormData(details); + + // Only issue tokens when 'application/x-www-form-urlencoded' or 'application/json' data parameters in POST body pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_ISSUING_CRITERIA.BODY_PARAMS, formData)) { + return false; + } } + this.issueInfo = { requestId: details.requestId, url: details.url }; + return true; } private matchesRedemptionCriteria( - details: chrome.webRequest.WebRequestBodyDetails, - url: URL, - formData: { [key: string]: string[] | string } + details: chrome.webRequest.WebRequestBodyDetails, ): boolean { + // Only redeem tokens for POST requests that contain data in body. if ( (details.method.toUpperCase() !== 'POST' ) || @@ -201,6 +198,8 @@ export class HcaptchaProvider extends Provider { return false; } + const url: URL = new URL(details.url); + // Only redeem tokens to hosts belonging to the provider. if (!isIssuingHostname(ALL_REDEMPTION_CRITERIA.HOSTNAMES, url)) { return false; @@ -216,11 +215,18 @@ export class HcaptchaProvider extends Provider { return false; } - // Only redeem tokens when 'application/x-www-form-urlencoded' data parameters in POST body pass defined criteria. - if (!areQualifiedBodyFormParams(ALL_REDEMPTION_CRITERIA.BODY_PARAMS, formData)) { - return false; + // conditionally short-circuit an expensive operation + if (ALL_REDEMPTION_CRITERIA.BODY_PARAMS !== undefined) { + const formData: { [key: string]: string[] | string } = getNormalizedFormData(details); + + // Only redeem tokens when 'application/x-www-form-urlencoded' or 'application/json' data parameters in POST body pass defined criteria. + if (!areQualifiedBodyFormParams(ALL_REDEMPTION_CRITERIA.BODY_PARAMS, formData)) { + return false; + } } + this.redeemInfo = { requestId: details.requestId }; + return true; } diff --git a/src/background/providers/provider.ts b/src/background/providers/provider.ts index 3fdeb7a7..968e65e0 100644 --- a/src/background/providers/provider.ts +++ b/src/background/providers/provider.ts @@ -1,4 +1,5 @@ import { Storage } from '../storage'; +import qs from 'qs'; export interface Callbacks { updateIcon(text: string): void; @@ -171,3 +172,61 @@ export function areQualifiedBodyFormParams(params: QUALIFIED_PARAMS | void, form const test: (param: string) => boolean = isQualifiedBodyFormParam.bind(null, formData); return areQualifiedParamsFound(params, test, true); } + +export function getNormalizedFormData( + details: chrome.webRequest.WebRequestBodyDetails, + flatten: boolean = false, +): { [key: string]: string[] | string } { + let formData: { [key: string]: string[] | string } = {}; + + if (details.requestBody instanceof Object) { + if (details.requestBody.formData instanceof Object) { + formData = details.requestBody.formData; + } + else if (Array.isArray(details.requestBody.raw) && (details.requestBody.raw.length > 0)) { + try { + const decodedData = details.requestBody.raw.map(val => (val && val.bytes) ? new TextDecoder().decode(new Uint8Array(val.bytes!)) : '').join(''); + let isParsed: boolean = false; + + if (!isParsed) { + // content-type: application/x-www-form-urlencoded + try { + const parsedData: any = qs.parse(decodedData); + + if ((parsedData !== undefined) && (parsedData instanceof Object)) { + isParsed = true; + formData = parsedData; + } + } + catch(e1) {} + } + + if (!isParsed) { + // content-type: application/json + try { + const parsedData: any = JSON.parse(decodedData); + + if ((parsedData !== undefined) && (parsedData instanceof Object)) { + formData = parsedData; + } + } + catch(e2) {} + } + } + catch(error) {} + } + } + + if (flatten) { + for (const key in formData) { + if ( + Array.isArray(formData[key]) && + (formData[key].length === 1) + ) { + formData[key] = formData[key][0]; + } + } + } + + return formData; +} From 0ab56ceb703d3aca462a014df5be82472eb75fc0 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Mon, 31 Jan 2022 23:51:33 -0800 Subject: [PATCH 32/36] fix the ability to restore tokens from a JSON text file previous methodology: ===================== * popup window: - dynamically creates an input file element - click event triggers the element to open the file chooser dialog - processes the list of selected files by: * reading the content of each file * parsing its JSON * passing the resulting object in a message to the background page * background page: - merges backup data with the tokens already saved in local storage - updates local storage with this new aggregate value - updates the popup window problem with previous methodology: ================================== * only works if the browser doesn't close the popup window before the data is passed to the background page * many browsers do close the popup window when the file input dialog is triggered for selection of input files new methodology: ================ * popup window: - sends a message to the background page * background page: - opens a new tab and loads a static html page * static html page: - dynamically creates an input file element - adds a click event handler to the element, which requires user interaction to trigger - the click event handler processes the list of selected files by: * reading the content of each file * parsing its JSON * passing the resulting object in a message to the background page - the click event handler also tracks the count of files pending * after the processing of all files is complete, sends a final message to the background page * background page: - merges backup data with the tokens already saved in local storage - updates local storage with this new aggregate value - closes the tab containing the static html page comparison between methodologies: ================================= * previous methodology: - pros: * simpler implementation * more elegant user experience - cons: * doesn't work in many browsers * new methodology: - pros: * works in all supported browsers - cons: * much more complicated implementation * less elegant user experience, which requires interaction with a standalone page in a new tab --- package.json | 2 +- public/manifest.json | 2 +- public/restore.html | 9 +++ src/background/listeners/messageListener.ts | 51 +++++++++++--- src/popup/store.ts | 46 +----------- src/restore/index.ts | 78 +++++++++++++++++++++ src/restore/tsconfig.json | 9 +++ tsconfig.json | 3 + webpack.config.js | 22 +++++- 9 files changed, 163 insertions(+), 59 deletions(-) create mode 100644 public/restore.html create mode 100644 src/restore/index.ts create mode 100644 src/restore/tsconfig.json diff --git a/package.json b/package.json index b9f02b7d..4c47a2a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.5", + "version": "3.6.6", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index ed8bcc2c..8570e770 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.5", + "version": "3.6.6", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/public/restore.html b/public/restore.html new file mode 100644 index 00000000..47771f4b --- /dev/null +++ b/public/restore.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/src/background/listeners/messageListener.ts b/src/background/listeners/messageListener.ts index 4aca5077..301d3f4a 100644 --- a/src/background/listeners/messageListener.ts +++ b/src/background/listeners/messageListener.ts @@ -55,7 +55,21 @@ function addPasses(providerID: string, newTokensArray: string[]): boolean { } } -export function handleReceivedMessage(request: any, _sender: chrome.runtime.MessageSender, sendResponse: Function): void { +function restorePasses(backup: {[key: string]: string[]} | void): boolean { + let did_restore: boolean = false; + + if (backup !== undefined) { + for (const providerID in backup) { + if (addPasses(providerID, backup[providerID]) && !did_restore) { + did_restore = true; + } + } + } + + return did_restore; +} + +export function handleReceivedMessage(request: any, sender: chrome.runtime.MessageSender, sendResponse: Function): void { // ------------------------------------------------------------------------- if (request.tokensCount === true) { @@ -80,23 +94,37 @@ export function handleReceivedMessage(request: any, _sender: chrome.runtime.Mess // ------------------------------------------------------------------------- if (request.restore === true) { - const backup: {[key: string]: string[]} | void = request.backup; - let did_restore: boolean = false; + if (request.tab !== undefined) { + if (request.tab.open === true) { + chrome.tabs.create({ url: '/restore.html', active: true }); - if (backup !== undefined) { - for (const providerID in backup) { - if (addPasses(providerID, backup[providerID]) && !did_restore) { - did_restore = true; + sendResponse(); + return; + } + + if (request.tab.close === true) { + if ((sender.tab !== undefined) && (sender.tab.id !== undefined) && (sender.tab.id !== chrome.tabs.TAB_ID_NONE)) { + chrome.tabs.remove(sender.tab.id); } + + sendResponse(); + return; } } - if (did_restore) { - // Update the browser action icon after restoring tokens. - forceUpdateIcon(); + if (request.backup !== undefined) { + const did_restore: boolean = restorePasses(request.backup); + + if (did_restore) { + // Update the browser action icon after restoring tokens. + forceUpdateIcon(); + } + + sendResponse(did_restore); + return; } - sendResponse(did_restore); + sendResponse(); return; } @@ -107,6 +135,7 @@ export function handleReceivedMessage(request: any, _sender: chrome.runtime.Mess // Update the browser action icon after clearing the tokens. forceUpdateIcon(); + sendResponse(); return; } diff --git a/src/popup/store.ts b/src/popup/store.ts index f3e3bbd0..ff20828d 100644 --- a/src/popup/store.ts +++ b/src/popup/store.ts @@ -71,51 +71,7 @@ const reducer = (state: any | undefined, action: Action) => { }); return state; case 'RESTORE_TOKENS': - (async (): Promise => { - - const readFile = function (event: Event) { - event.stopPropagation(); - event.stopImmediatePropagation(); - - const input: HTMLInputElement = event.target; - const files: FileList | null = input.files; - if (files === null) return; - - for (let file_index=0; file_index < files.length; file_index++) { - const reader = new FileReader(); - - reader.onload = function(){ - try { - if ((typeof reader.result === 'string') && (reader.result.length > 0)) { - const backupJSON: string = reader.result; - const backup: {[key: string]: string[]} = JSON.parse(backupJSON); - - chrome.runtime.sendMessage({ restore: true, backup }, (response: any) => { - if (response === true) { - store.dispatch({ type: 'OBTAIN_STATE' }); - } - }); - } - } - catch(e) {} - }; - - reader.readAsText( - files[file_index] - ); - } - }; - - try { - const input = window.document.createElement('input'); - input.setAttribute('type', 'file'); - input.setAttribute('accept', 'text/plain, application/json, .txt, .json'); - input.addEventListener('change', readFile); - input.click(); - } - catch(e) {} - })(); - + chrome.runtime.sendMessage({ restore: true, tab: { open: true } }); return state; case 'CLEAR_TOKENS': chrome.runtime.sendMessage({ clear: true }); diff --git a/src/restore/index.ts b/src/restore/index.ts new file mode 100644 index 00000000..840113a0 --- /dev/null +++ b/src/restore/index.ts @@ -0,0 +1,78 @@ +function handleBackupFileImport(event: Event): void { + event.stopPropagation(); + event.stopImmediatePropagation(); + + const input: HTMLInputElement = event.target; + const files: FileList | null = input.files; + if (files === null) return; + + let remaining_files = files.length; + + const onFileReadComplete = () => { + remaining_files--; + + if (remaining_files <= 0) { + chrome.runtime.sendMessage({ restore: true, tab: { close: true } }); + } + } + + for (let file_index=0; file_index < files.length; file_index++) { + const reader = new FileReader(); + + reader.onload = function(){ + try { + if ((typeof reader.result === 'string') && (reader.result.length > 0)) { + const backupJSON: string = reader.result; + const backup: {[key: string]: string[]} = JSON.parse(backupJSON); + + chrome.runtime.sendMessage({ restore: true, backup }); + } + } + catch(e) {} + onFileReadComplete(); + }; + + reader.onerror = onFileReadComplete; + reader.onabort = onFileReadComplete; + + reader.readAsText( + files[file_index] + ); + } +} + + +window.addEventListener('DOMContentLoaded', (event) => { + event.stopPropagation(); + event.stopImmediatePropagation(); + + const appName = chrome.i18n.getMessage('appName'); + const ctaRestorePasses = chrome.i18n.getMessage('ctaRestorePasses'); + + window.document.title = appName + ': ' + ctaRestorePasses; + + const input = window.document.createElement('input'); + input.setAttribute('type', 'file'); + input.setAttribute('accept', 'text/plain, application/json, .txt, .json'); + input.setAttribute('multiple', ''); + input.addEventListener('change', handleBackupFileImport); + + const root = window.document.getElementById('root'); + if (root !== null) { + const heading = window.document.createElement('h2'); + heading.appendChild( + window.document.createTextNode(appName) + ); + + const subheading = window.document.createElement('h3'); + subheading.appendChild( + window.document.createTextNode(ctaRestorePasses) + ); + + root.appendChild(heading); + root.appendChild(subheading); + root.appendChild(input); + } + + input.click(); +}); diff --git a/src/restore/tsconfig.json b/src/restore/tsconfig.json new file mode 100644 index 00000000..f7ea8933 --- /dev/null +++ b/src/restore/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "isolatedModules": false + }, + "extends": "../../tsconfig.json", + "include": [ + "." + ] +} diff --git a/tsconfig.json b/tsconfig.json index d17b7e34..bac37265 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,9 @@ }, { "path": "./src/popup" + }, + { + "path": "./src/restore" } ] } diff --git a/webpack.config.js b/webpack.config.js index d46ee954..f1b0dcd4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -90,5 +90,25 @@ const popup = { ], }; +const restore = { + ...common, + entry: { + restore: path.resolve('src/restore/index.ts'), + }, + module: { + rules: [tsloader], + }, + resolve: { + extensions: ['.ts', '.js'], + }, + plugins: [ + new HtmlWebpackPlugin({ + chunks: ['restore'], + filename: 'restore.html', + template: 'public/restore.html', + }), + ], +}; + // Mutiple targets for webpack: https://webpack.js.org/concepts/targets/#multiple-targets -export default [background, popup]; +export default [background, popup, restore]; From 70578595c89d2907c5231c0de9fbdb6c270edbb3 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 23 Feb 2022 18:03:25 -0800 Subject: [PATCH 33/36] add i18n translations using IBM Watson Language Translator service probably not perfect, but hopefully a good starting point; PRs are welcome. --- .gitignore | 2 + package-lock.json | 2615 ++++++++++++++------------- package.json | 7 +- public/_locales/ar/messages.json | 46 + public/_locales/bg/messages.json | 46 + public/_locales/bn/messages.json | 46 + public/_locales/bs/messages.json | 46 + public/_locales/cnr/messages.json | 46 + public/_locales/cs/messages.json | 46 + public/_locales/cy/messages.json | 46 + public/_locales/da/messages.json | 46 + public/_locales/de/messages.json | 46 + public/_locales/el/messages.json | 46 + public/_locales/es/messages.json | 46 + public/_locales/et/messages.json | 46 + public/_locales/fi/messages.json | 46 + public/_locales/fr/messages.json | 46 + public/_locales/fr_CA/messages.json | 46 + public/_locales/ga/messages.json | 46 + public/_locales/gu/messages.json | 46 + public/_locales/he/messages.json | 46 + public/_locales/hi/messages.json | 46 + public/_locales/hr/messages.json | 46 + public/_locales/hu/messages.json | 46 + public/_locales/id/messages.json | 46 + public/_locales/it/messages.json | 46 + public/_locales/ja/messages.json | 46 + public/_locales/ko/messages.json | 46 + public/_locales/lt/messages.json | 46 + public/_locales/lv/messages.json | 46 + public/_locales/ml/messages.json | 46 + public/_locales/ms/messages.json | 46 + public/_locales/mt/messages.json | 46 + public/_locales/nb/messages.json | 46 + public/_locales/ne/messages.json | 46 + public/_locales/nl/messages.json | 46 + public/_locales/pl/messages.json | 46 + public/_locales/pt/messages.json | 46 + public/_locales/ro/messages.json | 46 + public/_locales/ru/messages.json | 46 + public/_locales/si/messages.json | 46 + public/_locales/sk/messages.json | 46 + public/_locales/sl/messages.json | 46 + public/_locales/sr/messages.json | 46 + public/_locales/sv/messages.json | 46 + public/_locales/ta/messages.json | 46 + public/_locales/te/messages.json | 46 + public/_locales/th/messages.json | 46 + public/_locales/tr/messages.json | 46 + public/_locales/uk/messages.json | 46 + public/_locales/ur/messages.json | 46 + public/_locales/vi/messages.json | 46 + public/_locales/zh/messages.json | 46 + public/_locales/zh_TW/messages.json | 46 + public/manifest.json | 2 +- 55 files changed, 3710 insertions(+), 1262 deletions(-) create mode 100644 public/_locales/ar/messages.json create mode 100644 public/_locales/bg/messages.json create mode 100644 public/_locales/bn/messages.json create mode 100644 public/_locales/bs/messages.json create mode 100644 public/_locales/cnr/messages.json create mode 100644 public/_locales/cs/messages.json create mode 100644 public/_locales/cy/messages.json create mode 100644 public/_locales/da/messages.json create mode 100644 public/_locales/de/messages.json create mode 100644 public/_locales/el/messages.json create mode 100644 public/_locales/es/messages.json create mode 100644 public/_locales/et/messages.json create mode 100644 public/_locales/fi/messages.json create mode 100644 public/_locales/fr/messages.json create mode 100644 public/_locales/fr_CA/messages.json create mode 100644 public/_locales/ga/messages.json create mode 100644 public/_locales/gu/messages.json create mode 100644 public/_locales/he/messages.json create mode 100644 public/_locales/hi/messages.json create mode 100644 public/_locales/hr/messages.json create mode 100644 public/_locales/hu/messages.json create mode 100644 public/_locales/id/messages.json create mode 100644 public/_locales/it/messages.json create mode 100644 public/_locales/ja/messages.json create mode 100644 public/_locales/ko/messages.json create mode 100644 public/_locales/lt/messages.json create mode 100644 public/_locales/lv/messages.json create mode 100644 public/_locales/ml/messages.json create mode 100644 public/_locales/ms/messages.json create mode 100644 public/_locales/mt/messages.json create mode 100644 public/_locales/nb/messages.json create mode 100644 public/_locales/ne/messages.json create mode 100644 public/_locales/nl/messages.json create mode 100644 public/_locales/pl/messages.json create mode 100644 public/_locales/pt/messages.json create mode 100644 public/_locales/ro/messages.json create mode 100644 public/_locales/ru/messages.json create mode 100644 public/_locales/si/messages.json create mode 100644 public/_locales/sk/messages.json create mode 100644 public/_locales/sl/messages.json create mode 100644 public/_locales/sr/messages.json create mode 100644 public/_locales/sv/messages.json create mode 100644 public/_locales/ta/messages.json create mode 100644 public/_locales/te/messages.json create mode 100644 public/_locales/th/messages.json create mode 100644 public/_locales/tr/messages.json create mode 100644 public/_locales/uk/messages.json create mode 100644 public/_locales/ur/messages.json create mode 100644 public/_locales/vi/messages.json create mode 100644 public/_locales/zh/messages.json create mode 100644 public/_locales/zh_TW/messages.json diff --git a/.gitignore b/.gitignore index 2b066c91..c685e391 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ /dist/PrivacyPass.xpi /dist/.bin/**/temp/ +/public/_locales/debug.en.txt + *.swp diff --git a/package-lock.json b/package-lock.json index 006e036b..3945f93f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "privacy-pass", - "version": "3.3.2", + "version": "3.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "privacy-pass", - "version": "3.3.2", + "version": "3.7.0", "license": "BSD-3-Clause", "dependencies": { "asn1-parser": "^1.1.8", @@ -29,6 +29,7 @@ "@types/react-dom": "^17.0.5", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", + "@warren-bank/translate-webextension-strings": "^1.0.0", "copy-webpack-plugin": "^8.1.1", "css-loader": "^5.2.4", "eslint": "^7.32.0", @@ -51,6 +52,18 @@ "webpack-cli": "^4.7.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -61,35 +74,35 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", + "version": "7.17.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", + "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", "dev": true, "dependencies": { + "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", + "@babel/generator": "^7.17.3", "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.3", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "semver": "^6.3.0" }, "engines": { "node": ">=6.9.0" @@ -120,22 +133,13 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.8", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -242,9 +246,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", @@ -253,8 +257,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -312,23 +316,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", "dev": true, "dependencies": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", @@ -411,9 +415,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -585,9 +589,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -622,19 +626,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", + "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -664,9 +668,9 @@ } }, "node_modules/@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", @@ -775,16 +779,16 @@ } }, "node_modules/@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0" }, "engines": { @@ -792,35 +796,35 @@ } }, "node_modules/@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", "micromatch": "^4.0.4", "rimraf": "^3.0.0", "slash": "^3.0.0", @@ -839,77 +843,77 @@ } }, "node_modules/@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", "dev": true, "dependencies": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6" + "jest-mock": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -929,13 +933,13 @@ } }, "node_modules/@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", "dev": true, "dependencies": { "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "source-map": "^0.6.0" }, "engines": { @@ -943,13 +947,13 @@ } }, "node_modules/@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -958,36 +962,36 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", "dev": true, "dependencies": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", "dev": true, "dependencies": { "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -999,9 +1003,9 @@ } }, "node_modules/@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -1014,6 +1018,31 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1128,9 +1157,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", - "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -1148,9 +1177,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "node_modules/@types/filesystem": { @@ -1223,12 +1252,12 @@ } }, "node_modules/@types/jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", - "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", + "version": "27.4.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz", + "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==", "dev": true, "dependencies": { - "jest-diff": "^27.0.0", + "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, @@ -1254,15 +1283,15 @@ } }, "node_modules/@types/node": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", - "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "node_modules/@types/prettier": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", + "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==", "dev": true }, "node_modules/@types/prop-types": { @@ -1277,9 +1306,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", + "version": "17.0.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", + "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1333,14 +1362,14 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", - "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz", + "integrity": "sha512-M499lqa8rnNK7mUv74lSFFttuUsubIRdAbHcVaP93oFcKkEmHmLqy2n7jM9C8DVmFMYK61ExrZU6dLYhQZmUpw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/type-utils": "5.10.0", - "@typescript-eslint/utils": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/type-utils": "5.12.1", + "@typescript-eslint/utils": "5.12.1", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", @@ -1366,14 +1395,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", - "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.12.1.tgz", + "integrity": "sha512-6LuVUbe7oSdHxUWoX/m40Ni8gsZMKCi31rlawBHt7VtW15iHzjbpj2WLiToG2758KjtCCiLRKZqfrOdl3cNKuw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/typescript-estree": "5.12.1", "debug": "^4.3.2" }, "engines": { @@ -1393,13 +1422,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", - "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.12.1.tgz", + "integrity": "sha512-J0Wrh5xS6XNkd4TkOosxdpObzlYfXjAFIm9QxYLCPOcHVv1FyyFCPom66uIh8uBr0sZCrtS+n19tzufhwab8ZQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0" + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/visitor-keys": "5.12.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1410,12 +1439,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", - "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.12.1.tgz", + "integrity": "sha512-Gh8feEhsNLeCz6aYqynh61Vsdy+tiNNkQtc+bN3IvQvRqHkXGUhYkUi+ePKzP0Mb42se7FDb+y2SypTbpbR/Sg==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.10.0", + "@typescript-eslint/utils": "5.12.1", "debug": "^4.3.2", "tsutils": "^3.21.0" }, @@ -1436,9 +1465,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", - "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.12.1.tgz", + "integrity": "sha512-hfcbq4qVOHV1YRdhkDldhV9NpmmAu2vp6wuFODL71Y0Ixak+FLeEU4rnPxgmZMnGreGEghlEucs9UZn5KOfHJA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1449,13 +1478,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", - "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.1.tgz", + "integrity": "sha512-ahOdkIY9Mgbza7L9sIi205Pe1inCkZWAHE1TV1bpxlU4RZNPtXaDZfiiFWcL9jdxvW1hDYZJXrFm+vlMkXRbBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/visitor-keys": "5.12.1", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -1476,15 +1505,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", - "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.12.1.tgz", + "integrity": "sha512-Qq9FIuU0EVEsi8fS6pG+uurbhNTtoYr4fq8tKjBupsK5Bgbk2I32UGm0Sh+WOyjOPgo/5URbxxSNV6HYsxV4MQ==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/typescript-estree": "5.12.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -1500,12 +1529,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", - "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.1.tgz", + "integrity": "sha512-l1KSLfupuwrXx6wc0AuOmC7Ko5g14ZOQ86wJJqRbdLbXLK02pK/DPiDDqCc7BqqiiA04/eAA6ayL0bgOrAkH7A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/types": "5.12.1", "eslint-visitor-keys": "^3.0.0" }, "engines": { @@ -1516,6 +1545,37 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@warren-bank/ibm-watson-language-translator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@warren-bank/ibm-watson-language-translator/-/ibm-watson-language-translator-1.0.0.tgz", + "integrity": "sha512-K4KJorAm4K6YKljBIrmE2j7wP72z7nlfrppC0OKTs7tP+iMInZ6lYhhPIzp7MlsX/K0lt1sYZ2EsuWy+R0ffiA==", + "dev": true, + "dependencies": { + "@warren-bank/node-process-argv": "^1.1.0" + }, + "bin": { + "ibm-translate": "bin/ibm-translate.js" + } + }, + "node_modules/@warren-bank/node-process-argv": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@warren-bank/node-process-argv/-/node-process-argv-1.1.0.tgz", + "integrity": "sha512-81pXPEVDhM+HQNlXgj/Aa4ZQSXQfX79tvd8nPuHXTEMJ6pnvahiTh/4jkNwQrIznecIDZM+MFt6i2IZmeiYrvA==", + "dev": true + }, + "node_modules/@warren-bank/translate-webextension-strings": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@warren-bank/translate-webextension-strings/-/translate-webextension-strings-1.0.0.tgz", + "integrity": "sha512-QTefZz4fVv9tJ3xEkKELHsoak+GaSgEL1qx3oRcfOFbhYbc1+WDVsZNM7liSBkjt6sU4JzApNRqc83T0AwRALw==", + "dev": true, + "dependencies": { + "@warren-bank/ibm-watson-language-translator": "^1.0.0", + "@warren-bank/node-process-argv": "^1.1.0" + }, + "bin": { + "translate-webextension-strings": "bin/translate-webextension-strings.js" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -1663,9 +1723,9 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", - "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", "dev": true, "peerDependencies": { "webpack": "4.x.x || 5.x.x", @@ -1673,9 +1733,9 @@ } }, "node_modules/@webpack-cli/info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz", - "integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", "dev": true, "dependencies": { "envinfo": "^7.7.3" @@ -1685,9 +1745,9 @@ } }, "node_modules/@webpack-cli/serve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", - "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", "dev": true, "peerDependencies": { "webpack-cli": "4.x.x" @@ -1913,18 +1973,18 @@ } }, "node_modules/babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", "dev": true, "dependencies": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", + "babel-preset-jest": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { @@ -1951,9 +2011,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -1989,12 +2049,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^27.4.0", + "babel-plugin-jest-hoist": "^27.5.1", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -2082,15 +2142,15 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", "dev": true, "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" }, "bin": { @@ -2195,9 +2255,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001300", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", - "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", "dev": true, "funding": { "type": "opencollective", @@ -2278,9 +2338,9 @@ "dev": true }, "node_modules/clean-css": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", - "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz", + "integrity": "sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==", "dev": true, "dependencies": { "source-map": "~0.6.0" @@ -2603,9 +2663,9 @@ } }, "node_modules/diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -2731,9 +2791,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.48", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.48.tgz", - "integrity": "sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA==", + "version": "1.4.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", + "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==", "dev": true }, "node_modules/emittery": { @@ -2764,9 +2824,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz", + "integrity": "sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -2809,6 +2869,15 @@ "node": ">=4" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -2976,9 +3045,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.4.0.tgz", + "integrity": "sha512-CFotdUcMY18nGRo5KGsnNxpznzhkopOcOo0InID+sgQssPrzjvsyKZPvOgymTFeHrFuC3Tzdf2YndhXtULK9Iw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -3058,9 +3127,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3246,15 +3315,15 @@ } }, "node_modules/expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -3395,15 +3464,15 @@ } }, "node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "funding": [ { "type": "individual", @@ -3555,9 +3624,9 @@ "dev": true }, "node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3687,60 +3756,6 @@ "node": ">=12" } }, - "node_modules/html-minifier-terser/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/html-minifier-terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/html-minifier-terser/node_modules/terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "acorn": "^8.5.0" - }, - "peerDependenciesMeta": { - "acorn": { - "optional": true - } - } - }, - "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/html-webpack-plugin": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", @@ -3945,6 +3960,12 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4131,9 +4152,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4144,14 +4165,14 @@ } }, "node_modules/jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "dependencies": { - "@jest/core": "^27.4.7", + "@jest/core": "^27.5.1", "import-local": "^3.0.2", - "jest-cli": "^27.4.7" + "jest-cli": "^27.5.1" }, "bin": { "jest": "bin/jest.js" @@ -4169,12 +4190,12 @@ } }, "node_modules/jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "execa": "^5.0.0", "throat": "^6.0.1" }, @@ -4183,27 +4204,27 @@ } }, "node_modules/jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.4.6", + "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.3", "throat": "^6.0.1" @@ -4213,21 +4234,21 @@ } }, "node_modules/jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", "dev": true, "dependencies": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "prompts": "^2.0.1", "yargs": "^16.2.0" }, @@ -4247,33 +4268,35 @@ } }, "node_modules/jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", "dev": true, "dependencies": { "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -4288,24 +4311,24 @@ } }, "node_modules/jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -4315,33 +4338,33 @@ } }, "node_modules/jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", "jsdom": "^16.6.0" }, "engines": { @@ -4349,47 +4372,47 @@ } }, "node_modules/jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "micromatch": "^4.0.4", "walker": "^1.0.7" }, @@ -4401,27 +4424,27 @@ } }, "node_modules/jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", "dev": true, "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^27.4.6", + "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", "throat": "^6.0.1" }, "engines": { @@ -4429,46 +4452,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", "dev": true, "dependencies": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", + "pretty-format": "^27.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -4489,12 +4512,12 @@ } }, "node_modules/jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*" }, "engines": { @@ -4519,27 +4542,27 @@ } }, "node_modules/jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", "dev": true, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" @@ -4549,45 +4572,44 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", "dev": true, "dependencies": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "source-map-support": "^0.5.6", "throat": "^6.0.1" }, @@ -4596,31 +4618,31 @@ } }, "node_modules/jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -4629,22 +4651,22 @@ } }, "node_modules/jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", "dev": true, "dependencies": { "@types/node": "*", - "graceful-fs": "^4.2.4" + "graceful-fs": "^4.2.9" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", "dev": true, "dependencies": { "@babel/core": "^7.7.2", @@ -4652,22 +4674,22 @@ "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/babel__traverse": "^7.0.4", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", + "pretty-format": "^27.5.1", "semver": "^7.3.2" }, "engines": { @@ -4675,16 +4697,16 @@ } }, "node_modules/jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" }, "engines": { @@ -4692,17 +4714,17 @@ } }, "node_modules/jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", "dev": true, "dependencies": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "jest-get-type": "^27.5.1", "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "pretty-format": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" @@ -4721,17 +4743,17 @@ } }, "node_modules/jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", "dev": true, "dependencies": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.4.2", + "jest-util": "^27.5.1", "string-length": "^4.0.1" }, "engines": { @@ -4739,9 +4761,9 @@ } }, "node_modules/jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "dependencies": { "@types/node": "*", @@ -4861,6 +4883,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4951,6 +4979,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -5161,9 +5195,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -5185,9 +5219,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -5240,9 +5274,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", "dev": true }, "node_modules/normalize-path": { @@ -5414,6 +5448,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -5491,9 +5543,9 @@ } }, "node_modules/pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true, "engines": { "node": ">= 6" @@ -5512,14 +5564,14 @@ } }, "node_modules/postcss": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", - "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", + "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", "dev": true, "dependencies": { - "nanoid": "^3.1.30", + "nanoid": "^3.2.0", "picocolors": "^1.0.0", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5589,9 +5641,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", - "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", + "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -5651,9 +5703,9 @@ } }, "node_modules/pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "dependencies": { "ansi-regex": "^5.0.1", @@ -5928,12 +5980,12 @@ } }, "node_modules/resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "dependencies": { - "is-core-module": "^2.8.0", + "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -6062,9 +6114,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz", - "integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==", + "version": "1.49.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", + "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -6075,7 +6127,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" } }, "node_modules/sass-loader": { @@ -6222,9 +6274,9 @@ } }, "node_modules/signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "node_modules/sisteransi": { @@ -6484,9 +6536,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -6530,13 +6582,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz", + "integrity": "sha512-uCA9DLanzzWSsN1UirKwylhhRz3aKPInlfmpGfw8VN6jHsAtu8HJtIpeeHHK23rxnE/cDc+yvmq5wqkIC6Kn0A==", + "dev": true, + "dependencies": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/terser-webpack-plugin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", - "integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", + "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", "dev": true, "dependencies": { - "jest-worker": "^27.4.1", + "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", @@ -6564,13 +6634,20 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/acorn": { + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/terser/node_modules/acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, - "optional": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6578,47 +6655,13 @@ "node": ">=0.4.0" } }, - "node_modules/terser-webpack-plugin/node_modules/commander": { + "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "acorn": "^8.5.0" - }, - "peerDependenciesMeta": { - "acorn": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { + "node_modules/terser/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", @@ -6883,9 +6926,9 @@ } }, "node_modules/typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -7006,13 +7049,13 @@ } }, "node_modules/webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", + "version": "5.69.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", + "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -7034,7 +7077,7 @@ "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", "watchpack": "^2.3.1", - "webpack-sources": "^3.2.2" + "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" @@ -7053,15 +7096,15 @@ } }, "node_modules/webpack-cli": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", - "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.0", - "@webpack-cli/info": "^1.4.0", - "@webpack-cli/serve": "^1.6.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", "colorette": "^2.0.14", "commander": "^7.0.0", "execa": "^5.0.0", @@ -7252,9 +7295,9 @@ } }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", "dev": true, "engines": { "node": ">=8.3.0" @@ -7340,6 +7383,15 @@ } }, "dependencies": { + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.0" + } + }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -7350,32 +7402,32 @@ } }, "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", "dev": true }, "@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", + "version": "7.17.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", + "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", "dev": true, "requires": { + "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", + "@babel/generator": "^7.17.3", "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.3", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "semver": "^6.3.0" }, "dependencies": { "@babel/code-frame": { @@ -7392,22 +7444,16 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", "dev": true, "requires": { - "@babel/types": "^7.16.8", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -7488,9 +7534,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.16.7", @@ -7499,8 +7545,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-plugin-utils": { @@ -7540,20 +7586,20 @@ "dev": true }, "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", "dev": true, "requires": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" } }, "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", @@ -7620,9 +7666,9 @@ } }, "@babel/parser": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz", - "integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -7743,9 +7789,9 @@ } }, "@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -7773,19 +7819,19 @@ } }, "@babel/traverse": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz", - "integrity": "sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", + "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.8", - "@babel/types": "^7.16.8", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -7808,9 +7854,9 @@ } }, "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", @@ -7899,49 +7945,49 @@ "dev": true }, "@jest/console": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz", - "integrity": "sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0" } }, "@jest/core": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz", - "integrity": "sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/reporters": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.8.1", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.4.2", - "jest-config": "^27.4.7", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-resolve-dependencies": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", - "jest-watcher": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", "micromatch": "^4.0.4", "rimraf": "^3.0.0", "slash": "^3.0.0", @@ -7949,68 +7995,68 @@ } }, "@jest/environment": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz", - "integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", "dev": true, "requires": { - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6" + "jest-mock": "^27.5.1" } }, "@jest/fake-timers": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz", - "integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@sinonjs/fake-timers": "^8.0.1", "@types/node": "*", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" } }, "@jest/globals": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz", - "integrity": "sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/types": "^27.4.2", - "expect": "^27.4.6" + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" } }, "@jest/reporters": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz", - "integrity": "sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -8019,56 +8065,56 @@ } }, "@jest/source-map": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz", - "integrity": "sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", "dev": true, "requires": { "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "source-map": "^0.6.0" } }, "@jest/test-result": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz", - "integrity": "sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz", - "integrity": "sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", "dev": true, "requires": { - "@jest/test-result": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-runtime": "^27.4.6" + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" } }, "@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -8077,9 +8123,9 @@ } }, "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -8089,6 +8135,28 @@ "chalk": "^4.0.0" } }, + "@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8191,9 +8259,9 @@ } }, "@types/eslint": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", - "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", "dev": true, "requires": { "@types/estree": "*", @@ -8211,9 +8279,9 @@ } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "@types/filesystem": { @@ -8286,12 +8354,12 @@ } }, "@types/jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", - "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", + "version": "27.4.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz", + "integrity": "sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==", "dev": true, "requires": { - "jest-diff": "^27.0.0", + "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" } }, @@ -8317,15 +8385,15 @@ } }, "@types/node": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", - "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "@types/prettier": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz", - "integrity": "sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.4.tgz", + "integrity": "sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==", "dev": true }, "@types/prop-types": { @@ -8340,9 +8408,9 @@ "dev": true }, "@types/react": { - "version": "17.0.38", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", - "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", + "version": "17.0.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", + "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -8396,14 +8464,14 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz", - "integrity": "sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.1.tgz", + "integrity": "sha512-M499lqa8rnNK7mUv74lSFFttuUsubIRdAbHcVaP93oFcKkEmHmLqy2n7jM9C8DVmFMYK61ExrZU6dLYhQZmUpw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/type-utils": "5.10.0", - "@typescript-eslint/utils": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/type-utils": "5.12.1", + "@typescript-eslint/utils": "5.12.1", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", @@ -8413,52 +8481,52 @@ } }, "@typescript-eslint/parser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.0.tgz", - "integrity": "sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.12.1.tgz", + "integrity": "sha512-6LuVUbe7oSdHxUWoX/m40Ni8gsZMKCi31rlawBHt7VtW15iHzjbpj2WLiToG2758KjtCCiLRKZqfrOdl3cNKuw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/typescript-estree": "5.12.1", "debug": "^4.3.2" } }, "@typescript-eslint/scope-manager": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz", - "integrity": "sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.12.1.tgz", + "integrity": "sha512-J0Wrh5xS6XNkd4TkOosxdpObzlYfXjAFIm9QxYLCPOcHVv1FyyFCPom66uIh8uBr0sZCrtS+n19tzufhwab8ZQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0" + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/visitor-keys": "5.12.1" } }, "@typescript-eslint/type-utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz", - "integrity": "sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.12.1.tgz", + "integrity": "sha512-Gh8feEhsNLeCz6aYqynh61Vsdy+tiNNkQtc+bN3IvQvRqHkXGUhYkUi+ePKzP0Mb42se7FDb+y2SypTbpbR/Sg==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.10.0", + "@typescript-eslint/utils": "5.12.1", "debug": "^4.3.2", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.0.tgz", - "integrity": "sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.12.1.tgz", + "integrity": "sha512-hfcbq4qVOHV1YRdhkDldhV9NpmmAu2vp6wuFODL71Y0Ixak+FLeEU4rnPxgmZMnGreGEghlEucs9UZn5KOfHJA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz", - "integrity": "sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.1.tgz", + "integrity": "sha512-ahOdkIY9Mgbza7L9sIi205Pe1inCkZWAHE1TV1bpxlU4RZNPtXaDZfiiFWcL9jdxvW1hDYZJXrFm+vlMkXRbBw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/visitor-keys": "5.10.0", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/visitor-keys": "5.12.1", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -8467,29 +8535,54 @@ } }, "@typescript-eslint/utils": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.0.tgz", - "integrity": "sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.12.1.tgz", + "integrity": "sha512-Qq9FIuU0EVEsi8fS6pG+uurbhNTtoYr4fq8tKjBupsK5Bgbk2I32UGm0Sh+WOyjOPgo/5URbxxSNV6HYsxV4MQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.10.0", - "@typescript-eslint/types": "5.10.0", - "@typescript-eslint/typescript-estree": "5.10.0", + "@typescript-eslint/scope-manager": "5.12.1", + "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/typescript-estree": "5.12.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/visitor-keys": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz", - "integrity": "sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ==", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.1.tgz", + "integrity": "sha512-l1KSLfupuwrXx6wc0AuOmC7Ko5g14ZOQ86wJJqRbdLbXLK02pK/DPiDDqCc7BqqiiA04/eAA6ayL0bgOrAkH7A==", "dev": true, "requires": { - "@typescript-eslint/types": "5.10.0", + "@typescript-eslint/types": "5.12.1", "eslint-visitor-keys": "^3.0.0" } }, + "@warren-bank/ibm-watson-language-translator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@warren-bank/ibm-watson-language-translator/-/ibm-watson-language-translator-1.0.0.tgz", + "integrity": "sha512-K4KJorAm4K6YKljBIrmE2j7wP72z7nlfrppC0OKTs7tP+iMInZ6lYhhPIzp7MlsX/K0lt1sYZ2EsuWy+R0ffiA==", + "dev": true, + "requires": { + "@warren-bank/node-process-argv": "^1.1.0" + } + }, + "@warren-bank/node-process-argv": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@warren-bank/node-process-argv/-/node-process-argv-1.1.0.tgz", + "integrity": "sha512-81pXPEVDhM+HQNlXgj/Aa4ZQSXQfX79tvd8nPuHXTEMJ6pnvahiTh/4jkNwQrIznecIDZM+MFt6i2IZmeiYrvA==", + "dev": true + }, + "@warren-bank/translate-webextension-strings": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@warren-bank/translate-webextension-strings/-/translate-webextension-strings-1.0.0.tgz", + "integrity": "sha512-QTefZz4fVv9tJ3xEkKELHsoak+GaSgEL1qx3oRcfOFbhYbc1+WDVsZNM7liSBkjt6sU4JzApNRqc83T0AwRALw==", + "dev": true, + "requires": { + "@warren-bank/ibm-watson-language-translator": "^1.0.0", + "@warren-bank/node-process-argv": "^1.1.0" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -8637,25 +8730,25 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", - "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz", - "integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", - "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", "dev": true, "requires": {} }, @@ -8823,18 +8916,18 @@ } }, "babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", "dev": true, "requires": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", + "babel-preset-jest": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" } }, @@ -8852,9 +8945,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -8884,12 +8977,12 @@ } }, "babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^27.4.0", + "babel-plugin-jest-hoist": "^27.5.1", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -8948,15 +9041,15 @@ "dev": true }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" } }, @@ -9025,9 +9118,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001300", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", - "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", "dev": true }, "chalk": { @@ -9081,9 +9174,9 @@ "dev": true }, "clean-css": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", - "integrity": "sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz", + "integrity": "sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -9328,9 +9421,9 @@ "dev": true }, "diff-sequences": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", "dev": true }, "dir-glob": { @@ -9425,9 +9518,9 @@ } }, "electron-to-chromium": { - "version": "1.4.48", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.48.tgz", - "integrity": "sha512-RT3SEmpv7XUA+tKXrZGudAWLDpa7f8qmhjcLaM6OD/ERxjQ/zAojT8/Vvo0BSzbArkElFZ1WyZ9FuwAYbkdBNA==", + "version": "1.4.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz", + "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==", "dev": true }, "emittery": { @@ -9449,9 +9542,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz", + "integrity": "sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -9479,6 +9572,15 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -9637,9 +9739,9 @@ } }, "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.4.0.tgz", + "integrity": "sha512-CFotdUcMY18nGRo5KGsnNxpznzhkopOcOo0InID+sgQssPrzjvsyKZPvOgymTFeHrFuC3Tzdf2YndhXtULK9Iw==", "dev": true, "requires": {} }, @@ -9689,9 +9791,9 @@ } }, "eslint-visitor-keys": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", - "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { @@ -9795,15 +9897,15 @@ "dev": true }, "expect": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz", - "integrity": "sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "jest-get-type": "^27.4.0", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6" + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" } }, "fast-deep-equal": { @@ -9916,15 +10018,15 @@ } }, "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "form-data": { "version": "3.0.1", @@ -10025,9 +10127,9 @@ "dev": true }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -10121,41 +10223,6 @@ "param-case": "^3.0.4", "relateurl": "^0.2.7", "terser": "^5.10.0" - }, - "dependencies": { - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "optional": true, - "peer": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - } } }, "html-webpack-plugin": { @@ -10290,6 +10357,12 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10430,9 +10503,9 @@ } }, "istanbul-reports": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz", - "integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -10440,254 +10513,256 @@ } }, "jest": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz", - "integrity": "sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "requires": { - "@jest/core": "^27.4.7", + "@jest/core": "^27.5.1", "import-local": "^3.0.2", - "jest-cli": "^27.4.7" + "jest-cli": "^27.5.1" } }, "jest-changed-files": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz", - "integrity": "sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "execa": "^5.0.0", "throat": "^6.0.1" } }, "jest-circus": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz", - "integrity": "sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", - "expect": "^27.4.6", + "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.3", "throat": "^6.0.1" } }, "jest-cli": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz", - "integrity": "sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", "dev": true, "requires": { - "@jest/core": "^27.4.7", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.4.7", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "prompts": "^2.0.1", "yargs": "^16.2.0" } }, "jest-config": { - "version": "27.4.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz", - "integrity": "sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", "dev": true, "requires": { "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.4.6", - "@jest/types": "^27.4.2", - "babel-jest": "^27.4.6", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-circus": "^27.4.6", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-jasmine2": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-runner": "^27.4.6", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", - "slash": "^3.0.0" + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" } }, "jest-diff": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz", - "integrity": "sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, "jest-docblock": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz", - "integrity": "sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz", - "integrity": "sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6" + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" } }, "jest-environment-jsdom": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz", - "integrity": "sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", "jsdom": "^16.6.0" } }, "jest-environment-node": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz", - "integrity": "sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", - "jest-mock": "^27.4.6", - "jest-util": "^27.4.2" + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" } }, "jest-get-type": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", "dev": true }, "jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "micromatch": "^4.0.4", "walker": "^1.0.7" } }, "jest-jasmine2": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz", - "integrity": "sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", "dev": true, "requires": { - "@jest/environment": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^27.4.6", + "expect": "^27.5.1", "is-generator-fn": "^2.0.0", - "jest-each": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", - "pretty-format": "^27.4.6", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", "throat": "^6.0.1" } }, "jest-leak-detector": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz", - "integrity": "sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", "dev": true, "requires": { - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, "jest-matcher-utils": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz", - "integrity": "sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.6" + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" } }, "jest-message-util": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz", - "integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^27.4.6", + "pretty-format": "^27.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -10704,12 +10779,12 @@ } }, "jest-mock": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz", - "integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*" } }, @@ -10721,114 +10796,113 @@ "requires": {} }, "jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", "dev": true }, "jest-resolve": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz", - "integrity": "sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.4.2", - "jest-validate": "^27.4.6", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" } }, "jest-resolve-dependencies": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz", - "integrity": "sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", "dev": true, "requires": { - "@jest/types": "^27.4.2", - "jest-regex-util": "^27.4.0", - "jest-snapshot": "^27.4.6" + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" } }, "jest-runner": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz", - "integrity": "sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", "dev": true, "requires": { - "@jest/console": "^27.4.6", - "@jest/environment": "^27.4.6", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.4.0", - "jest-environment-jsdom": "^27.4.6", - "jest-environment-node": "^27.4.6", - "jest-haste-map": "^27.4.6", - "jest-leak-detector": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-resolve": "^27.4.6", - "jest-runtime": "^27.4.6", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", "source-map-support": "^0.5.6", "throat": "^6.0.1" } }, "jest-runtime": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz", - "integrity": "sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.4.6", - "@jest/fake-timers": "^27.4.6", - "@jest/globals": "^27.4.6", - "@jest/source-map": "^27.4.0", - "@jest/test-result": "^27.4.6", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dev": true, + "requires": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-mock": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-resolve": "^27.4.6", - "jest-snapshot": "^27.4.6", - "jest-util": "^27.4.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", "dev": true, "requires": { "@types/node": "*", - "graceful-fs": "^4.2.4" + "graceful-fs": "^4.2.9" } }, "jest-snapshot": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz", - "integrity": "sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", "dev": true, "requires": { "@babel/core": "^7.7.2", @@ -10836,51 +10910,51 @@ "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", "@types/babel__traverse": "^7.0.4", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.4.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.4.6", - "jest-get-type": "^27.4.0", - "jest-haste-map": "^27.4.6", - "jest-matcher-utils": "^27.4.6", - "jest-message-util": "^27.4.6", - "jest-util": "^27.4.2", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^27.4.6", + "pretty-format": "^27.5.1", "semver": "^7.3.2" } }, "jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "jest-validate": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz", - "integrity": "sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", "dev": true, "requires": { - "@jest/types": "^27.4.2", + "@jest/types": "^27.5.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.4.0", + "jest-get-type": "^27.5.1", "leven": "^3.1.0", - "pretty-format": "^27.4.6" + "pretty-format": "^27.5.1" }, "dependencies": { "camelcase": { @@ -10892,24 +10966,24 @@ } }, "jest-watcher": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz", - "integrity": "sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", "dev": true, "requires": { - "@jest/test-result": "^27.4.6", - "@jest/types": "^27.4.2", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.4.2", + "jest-util": "^27.5.1", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { "@types/node": "*", @@ -10998,6 +11072,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11063,6 +11143,12 @@ "type-check": "~0.4.0" } }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -11226,9 +11312,9 @@ } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -11247,9 +11333,9 @@ "dev": true }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, "natural-compare": { @@ -11291,9 +11377,9 @@ "dev": true }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", "dev": true }, "normalize-path": { @@ -11422,6 +11508,18 @@ "callsites": "^3.0.0" } }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -11481,9 +11579,9 @@ "dev": true }, "pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, "pkg-dir": { @@ -11496,14 +11594,14 @@ } }, "postcss": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", - "integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", + "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", "dev": true, "requires": { - "nanoid": "^3.1.30", + "nanoid": "^3.2.0", "picocolors": "^1.0.0", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" } }, "postcss-modules-extract-imports": { @@ -11543,9 +11641,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz", - "integrity": "sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ==", + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", + "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -11590,9 +11688,9 @@ } }, "pretty-format": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz", - "integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "requires": { "ansi-regex": "^5.0.1", @@ -11792,12 +11890,12 @@ "dev": true }, "resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "requires": { - "is-core-module": "^2.8.0", + "is-core-module": "^2.8.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -11883,9 +11981,9 @@ "dev": true }, "sass": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz", - "integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==", + "version": "1.49.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", + "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -11985,9 +12083,9 @@ } }, "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "sisteransi": { @@ -12177,9 +12275,9 @@ }, "dependencies": { "ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -12212,26 +12310,23 @@ "supports-hyperlinks": "^2.0.0" } }, - "terser-webpack-plugin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz", - "integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==", + "terser": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz", + "integrity": "sha512-uCA9DLanzzWSsN1UirKwylhhRz3aKPInlfmpGfw8VN6jHsAtu8HJtIpeeHHK23rxnE/cDc+yvmq5wqkIC6Kn0A==", "dev": true, "requires": { - "jest-worker": "^27.4.1", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" }, "dependencies": { "acorn": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "commander": { "version": "2.20.3", @@ -12239,6 +12334,27 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", + "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "dev": true, + "requires": { + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "dependencies": { "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -12247,25 +12363,6 @@ "requires": { "randombytes": "^2.1.0" } - }, - "terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } } } }, @@ -12455,9 +12552,9 @@ } }, "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, "universalify": { @@ -12555,13 +12652,13 @@ "dev": true }, "webpack": { - "version": "5.66.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.66.0.tgz", - "integrity": "sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg==", + "version": "5.69.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", + "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -12583,7 +12680,7 @@ "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", "watchpack": "^2.3.1", - "webpack-sources": "^3.2.2" + "webpack-sources": "^3.2.3" }, "dependencies": { "acorn": { @@ -12608,15 +12705,15 @@ } }, "webpack-cli": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", - "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.0", - "@webpack-cli/info": "^1.4.0", - "@webpack-cli/serve": "^1.6.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", "colorette": "^2.0.14", "commander": "^7.0.0", "execa": "^5.0.0", @@ -12732,9 +12829,9 @@ } }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 4c47a2a2..46ada648 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.6.6", + "version": "3.7.0", "private": true, "contributors": [ "Suphanat Chunhapanya ", @@ -17,7 +17,9 @@ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", "lint": "eslint ./src/**/*.{ts,tsx}", "clean": "rimraf lib && rimraf dist/PrivacyPass && rimraf dist/PrivacyPass.crx* && rimraf dist/PrivacyPass.xpi", - "sjcl": "cd node_modules/sjcl && perl configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js" + "sjcl": "cd node_modules/sjcl && perl configure --without-all --with-ecc --with-convenience --with-codecBytes --with-codecHex --compress=none && make sjcl.js", + "translate": "translate-webextension-strings -i \"en\" -f \"public/_locales/en/messages.json\" -d \"public/_locales\" -m -b \"Privacy Pass\" -b \"'manifest.json'\" -b \"'@popup[^']+'\" -b \"JSON\" -b \"Cloudflare\" -b \"hCaptcha\" -b \"Github\"", + "translate:debug": "translate-webextension-strings -i \"en\" -o \"en\" -f \"public/_locales/en/messages.json\" -d \"public/_locales\" --debug -b \"Privacy Pass\" -b \"'manifest.json'\" -b \"'@popup[^']+'\" -b \"JSON\" -b \"Cloudflare\" -b \"hCaptcha\" -b \"Github\"" }, "dependencies": { "asn1-parser": "^1.1.8", @@ -40,6 +42,7 @@ "@types/react-dom": "^17.0.5", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", + "@warren-bank/translate-webextension-strings": "^1.0.0", "copy-webpack-plugin": "^8.1.1", "css-loader": "^5.2.4", "eslint": "^7.32.0", diff --git a/public/_locales/ar/messages.json b/public/_locales/ar/messages.json new file mode 100644 index 00000000..068b16f4 --- /dev/null +++ b/public/_locales/ar/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "اسم الامتداد المتضمن بواسطة 'manifest.json', وعرضه بواسطة '@popup/components/Header'." + }, + "appDescription": { + "message": "دعم Client ل ـ Privacy Pass بروتوكول التفويض غير المعروف.", + "description": "وصف اللاحقة المتضمنة بواسطة 'manifest.json'." + }, + "labelFileBackup": { + "message": "نسخة احتياطية", + "description": "العلامة المميزة المتضمنة في اسم الملف المفترض JSON عمل نسخ احتياطية بواسطة '@popup/store'." + }, + "labelAppVersion": { + "message": "النسخة", + "description": "التسمية الخاصة بنسخة الامتداد الحالية التي يتم عرضها بواسطة '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "اسم Cloudflare عرض جهة الاتاحة بواسطة '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "اسم hCaptcha عرض جهة الاتاحة بواسطة '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "الحصول على المزيد من التصاريح!", + "description": "يتم عرض نص mouseover بواسطة '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "عمل نسخة احتياطية لكل المؤخرات", + "description": "معروض بواسطة '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "استعادة P من النسخة الاحتياطية", + "description": "معروض بواسطة '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "محو كل الحمير", + "description": "معروض بواسطة '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "مشاهدة على Github", + "description": "معروض بواسطة '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/bg/messages.json b/public/_locales/bg/messages.json new file mode 100644 index 00000000..1e70021b --- /dev/null +++ b/public/_locales/bg/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "име на разширението, включено в 'manifest.json', и се показва от '@popup/components/Header'." + }, + "appDescription": { + "message": "Клиентска поддръжка за Privacy Pass Анонимно разрешение протокол.", + "description": "описание на разширението, включено в 'manifest.json'." + }, + "labelFileBackup": { + "message": "архивиране.", + "description": "етикет, включен в името на файла по подразбиране на JSON резервни копия, генерирани от '@popup/store'." + }, + "labelAppVersion": { + "message": "Версия:", + "description": "етикет на текущата версия на разширение, показвана от '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Име: Cloudflare доставчик, показан от '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Име: hCaptcha доставчик, показан от '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Още пасове.!", + "description": "mouseover текст, показан от '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Архивиране на всички пропуски", + "description": "се показва от '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Възстановяване на пропуски от архив", + "description": "се показва от '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Изчистете всички.", + "description": "се показва от '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Преглед на: Github", + "description": "се показва от '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/bn/messages.json b/public/_locales/bn/messages.json new file mode 100644 index 00000000..9c4a8728 --- /dev/null +++ b/public/_locales/bn/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "এর মধ্যে অন্তর্ভুক্ত এক্সটেনশনের নাম 'manifest.json', এবং প্রদর্শিত '@popup/components/Header'." + }, + "appDescription": { + "message": "ক্লায়েন্ট সমর্থন Privacy Pass অজ্ঞাতনামা অনুমোদনের প্রোটোকল.", + "description": "এক্সটেনশন এর বর্ণনা 'manifest.json'." + }, + "labelFileBackup": { + "message": "ব্যাকআপ", + "description": "লেবেল অন্তর্ভুক্ত ছিল ডিফল্ট ফাইলের নাম JSON @ info: status '@popup/store'." + }, + "labelAppVersion": { + "message": "সংস্করণ", + "description": "দ্বারা প্রদর্শিত বর্তমান এক্সটেনশন সংস্করণের লেবেল '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "নাম: Cloudflare প্রদানকারী দ্বারা প্রদর্শিত '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "নাম: hCaptcha প্রদানকারী দ্বারা প্রদর্শিত '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "আরো পাশ নিয়ে যাও!", + "description": "দ্বারা প্রদর্শিত মুখ্য লেখা '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "সমস্ত পাসওয়ার্ড ব্যাকআপ করো", + "description": "দ্বারা প্রদর্শিত '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "ব্যাকআপ থেকে পাসওয়ার্ড পুনরুদ্ধার করুন", + "description": "দ্বারা প্রদর্শিত '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "সমস্ত পাসওয়ার্ড মুছে ফেলুন", + "description": "দ্বারা প্রদর্শিত '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "ভিউ Github", + "description": "দ্বারা প্রদর্শিত '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/bs/messages.json b/public/_locales/bs/messages.json new file mode 100644 index 00000000..d2b4540e --- /dev/null +++ b/public/_locales/bs/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ime produžetka uključeno u 'manifest.json', i prikazuje ih '@popup/components/Header'." + }, + "appDescription": { + "message": "Podrška klijentu za Privacy Pass anonimni autorizacijski protokol.", + "description": "opis produženja uključenog u 'manifest.json'." + }, + "labelFileBackup": { + "message": "sigurnosna kopija", + "description": "oznaka uključena u default ime datoteke JSON sigurnosne kopije generirane '@popup/store'." + }, + "labelAppVersion": { + "message": "Verzija", + "description": "oznaka trenutne verzije proširenja prikazanu '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ime od Cloudflare davatelj je prikazan '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ime od hCaptcha davatelj je prikazan '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Donesi još propusnica.!", + "description": "Tekst koji prikazuje tekst koji se prikazuje '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Napravite sigurnosnu kopiju.", + "description": "Prikazano u '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Vrati Propusnice Iz Backup-a", + "description": "Prikazano u '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Očisti Sve Propusnice", + "description": "Prikazano u '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Pogled na Github", + "description": "Prikazano u '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/cnr/messages.json b/public/_locales/cnr/messages.json new file mode 100644 index 00000000..8b9a0c0c --- /dev/null +++ b/public/_locales/cnr/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Ime produžetka uključeno u 'manifest.json', I prikazala ga je '@popup/components/Header'." + }, + "appDescription": { + "message": "Podrška klijentima za Privacy Pass Anonimni autorizacijski protokol..", + "description": "Prevod i adaptacija: Opis: 'manifest.json'." + }, + "labelFileBackup": { + "message": "Hvala.", + "description": "Oznaka je uključena u zadano ime. JSON Kopije koje su generirale '@popup/store'." + }, + "labelAppVersion": { + "message": "Verzija.", + "description": "Oznaka za trenutnu verziju produženja. '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "I ... Cloudflare Danilo Leksi '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "I ... hCaptcha Danilo Leksi '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Daj mi još propusnice.!", + "description": "Izgovarajući tekst koji se prikazuje '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sve propusnice.", + "description": "Prikazao je '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Povrati propusnice od pojačanja.", + "description": "Prikazao je '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Očistite sve propusnice.", + "description": "Prikazao je '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Pogled na Github", + "description": "Prikazao je '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/cs/messages.json b/public/_locales/cs/messages.json new file mode 100644 index 00000000..ad266353 --- /dev/null +++ b/public/_locales/cs/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "název rozšíření zahrnutého 'manifest.json', a zobrazí se '@popup/components/Header'." + }, + "appDescription": { + "message": "Podpora klienta pro Privacy Pass anonymní autorizační protokol.", + "description": "popis rozšíření zahrnutou 'manifest.json'." + }, + "labelFileBackup": { + "message": "zálohování", + "description": "popisek je obsažen ve výchozím názvu souboru JSON zálohy generované '@popup/store'." + }, + "labelAppVersion": { + "message": "Verze", + "description": "jmenovka aktuální verze rozšíření zobrazená pomocí '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "název Cloudflare poskytovatel zobrazený dle '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "název hCaptcha poskytovatel zobrazený dle '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Získat více průchodů!", + "description": "mouseover text zobrazený pomocí '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Zálohovat všechny pasy", + "description": "zobrazit podle '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Obnovit pasy ze zálohy", + "description": "zobrazit podle '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Vymazat všechny pasy", + "description": "zobrazit podle '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Pohled na Github", + "description": "zobrazit podle '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/cy/messages.json b/public/_locales/cy/messages.json new file mode 100644 index 00000000..721f4891 --- /dev/null +++ b/public/_locales/cy/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "enw estyniad wedi ei gynnwys gan 'manifest.json', ac fe'i dangosir gan '@popup/components/Header'." + }, + "appDescription": { + "message": "Cymorth cleient i Privacy Pass protocol awdurdodi anhysbys.", + "description": "disgrifiad o'r estyniad a gynhwysir ynddo 'manifest.json'." + }, + "labelFileBackup": { + "message": "wrth gefn", + "description": "label wedi ei gynnwys yn enw ffeil rhagosodedig JSON llif gefn a gynhyrchir gan '@popup/store'." + }, + "labelAppVersion": { + "message": "Fersiwn", + "description": "label y fersiwn estyniad cyfredol a ddangosir gan '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "enw Cloudflare darparwr a ddangosir gan '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "enw hCaptcha darparwr a ddangosir gan '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Estyn rhagor o docynnau!", + "description": "Dangos y llygoden dros destun a ddangosir gan '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Cadw Copi Wrth Gefn", + "description": "a ddangosir gan '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Adfer Y Cardiau Wrth Gefn", + "description": "a ddangosir gan '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Clirio'r Holl Docynnau", + "description": "a ddangosir gan '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Golwg ar Github", + "description": "a ddangosir gan '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/da/messages.json b/public/_locales/da/messages.json new file mode 100644 index 00000000..96a62abe --- /dev/null +++ b/public/_locales/da/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Navn på udvidelse inkluderet af 'manifest.json', og vises af '@popup/components/Header'." + }, + "appDescription": { + "message": "Kundesupport til Privacy Pass anonym godkendelsesprotokol.", + "description": "Beskrivelse af udvidelse inkluderet af 'manifest.json'." + }, + "labelFileBackup": { + "message": "sikkerhedskopi", + "description": "etiket, der er inkluderet i standardfilnavn på JSON backups genereret af '@popup/store'." + }, + "labelAppVersion": { + "message": "Version", + "description": "etiket for den aktuelle udvidelsesversion, der vises af '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "navn på Cloudflare Udbyder vist af '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "navn på hCaptcha Udbyder vist af '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Hent flere adgangskort!", + "description": "overtalens tekst, der vises af '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sikkerhedskopiér alle passager", + "description": "vises af '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Genindlæs adgangskoder fra backup", + "description": "vises af '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Ryd alle adgangskoder", + "description": "vises af '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vis på Github", + "description": "vises af '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/de/messages.json b/public/_locales/de/messages.json new file mode 100644 index 00000000..a213f3d9 --- /dev/null +++ b/public/_locales/de/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Name der Erweiterung, die 'manifest.json', und angezeigt von '@popup/components/Header'." + }, + "appDescription": { + "message": "Clientunterstützung für Privacy Pass anonymes Berechtigungsprotokoll.", + "description": "Beschreibung der Erweiterung, die von 'manifest.json'." + }, + "labelFileBackup": { + "message": "Sicherung", + "description": "Bezeichnung im Standarddateinamen von JSON Sicherungen generiert von '@popup/store'." + }, + "labelAppVersion": { + "message": "Version", + "description": "Bezeichnung der aktuellen Erweiterungsversion angezeigt von '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Name Cloudflare Provider angezeigt von '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Name hCaptcha Provider angezeigt von '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Weitere Arbeitsgänge abrufen!", + "description": "Mouseover-Text angezeigt von '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Alle Arbeitsgänge sichern", + "description": "angezeigt von '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Zurückspeichern von Sicherung aus Sicherung", + "description": "angezeigt von '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Alle Durchgänge löschen", + "description": "angezeigt von '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Anzeigen auf Github", + "description": "angezeigt von '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/el/messages.json b/public/_locales/el/messages.json new file mode 100644 index 00000000..03cdb65c --- /dev/null +++ b/public/_locales/el/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "όνομα της επέκτασης που περιλαμβάνεται από: 'manifest.json', και εμφανίζεται από: '@popup/components/Header'." + }, + "appDescription": { + "message": "Υποστήριξη πελατών για: Privacy Pass ανώνυμο πρωτόκολλο εξουσιοδότησης.", + "description": "περιγραφή της επέκτασης που περιλαμβάνεται: 'manifest.json'." + }, + "labelFileBackup": { + "message": "-backup", + "description": "ετικέτα που περιλαμβάνεται στο προεπιλεγμένο όνομα αρχείου JSON αντίγραφα ασφαλείας που δημιουργήθηκαν από: '@popup/store'." + }, + "labelAppVersion": { + "message": "Έκδοση:", + "description": "ετικέτα της τρέχουσας εκδοχής επέκτασης που εμφανίζεται από '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ονομασια: Cloudflare Ο παροχέας εμφανίζεται από '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ονομασια: hCaptcha Ο παροχέας εμφανίζεται από '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Πάρτε περισσότερα περάσματα!!", + "description": "κείμενο του mouseover που εμφανίζεται '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Εφεδρικά Όλα Τα Passes", + "description": "εμφανίζεται από: '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Επαναφορά Passes Από Εφεδρικά", + "description": "εμφανίζεται από: '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Εκκαθάριση όλων των Passes", + "description": "εμφανίζεται από: '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Προβολή σε: Github", + "description": "εμφανίζεται από: '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/es/messages.json b/public/_locales/es/messages.json new file mode 100644 index 00000000..e1e680c3 --- /dev/null +++ b/public/_locales/es/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nombre de la extensión incluida en 'manifest.json', y mostrado por '@popup/components/Header'." + }, + "appDescription": { + "message": "Soporte de cliente para Privacy Pass protocolo de autorización anónimo.", + "description": "descripción de la extensión incluida en 'manifest.json'." + }, + "labelFileBackup": { + "message": "copia de seguridad", + "description": "etiqueta incluida en el nombre de archivo predeterminado de JSON copias de seguridad generadas '@popup/store'." + }, + "labelAppVersion": { + "message": "Versión", + "description": "etiqueta de la versión de extensión actual visualizada por '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nombre del Cloudflare proveedor visualizado por '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nombre del hCaptcha proveedor visualizado por '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Obtener más pases!", + "description": "texto de Mouseover visualizado por '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Realizar copia de seguridad de todos", + "description": "visualizada por '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "La restauración pasa de la copia de seguridad", + "description": "visualizada por '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Borrar todos los pases", + "description": "visualizada por '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Ver el Github", + "description": "visualizada por '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/et/messages.json b/public/_locales/et/messages.json new file mode 100644 index 00000000..ddeb0a25 --- /dev/null +++ b/public/_locales/et/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "pikendamise nimi, mis on esitatud 'manifest.json', ja kuvatakse '@popup/components/Header'." + }, + "appDescription": { + "message": "Klienditugi Privacy Pass anonüümne autentimise.", + "description": "laiendamise kirjeldus, mis on esitatud 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "pealdis sisaldub vaikimisi failinimi JSON Varukoopiate genereerimisest '@popup/store'." + }, + "labelAppVersion": { + "message": "Versioon", + "description": "label of cure extension version kuvatakse '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Ravimi nimetus Cloudflare pakkuja kuvamine '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Ravimi nimetus hCaptcha pakkuja kuvamine '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Tooge rohkem läbipääsuload!", + "description": "mouseover tekst kuvatakse '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Varundamine", + "description": "kuvatav '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Taasta Varukoopia Varukoopia", + "description": "kuvatav '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Tühjendage kõik läbipääsuload", + "description": "kuvatav '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vaade Github", + "description": "kuvatav '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/fi/messages.json b/public/_locales/fi/messages.json new file mode 100644 index 00000000..b0a10860 --- /dev/null +++ b/public/_locales/fi/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Laajennuksen nimi, mukaan lukien 'manifest.json', Ja jotka tulevat näkyviin '@popup/components/Header'." + }, + "appDescription": { + "message": "Asiakkaan tuki Privacy Pass Nimetön valtuutuspöytäkirja.", + "description": "Kuvaus laajennuksesta, johon 'manifest.json'." + }, + "labelFileBackup": { + "message": "Tuki", + "description": "Nimiön oletustiedostonimi JSON Varmuuskopiot, '@popup/store'." + }, + "labelAppVersion": { + "message": "Versio", + "description": "Nykyisen laajennusversion nimiö '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Nimi: Cloudflare Toimittajan osoittama '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Nimi: hCaptcha Toimittajan osoittama '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Ota lisää passeja!", + "description": "Hiirulainen teksti näkyy '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Apujoukot Kaikki Passat", + "description": "On oltava '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Palauta Passar-varmistuskopio", + "description": "On oltava '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Tyhjennä kaikki", + "description": "On oltava '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Näytä Github", + "description": "On oltava '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/fr/messages.json b/public/_locales/fr/messages.json new file mode 100644 index 00000000..63cfccb5 --- /dev/null +++ b/public/_locales/fr/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Nom de l'extension incluse par 'manifest.json', Et affichés par '@popup/components/Header'." + }, + "appDescription": { + "message": "Prise en charge du client pour Privacy Pass Protocole d'autorisation anonyme.", + "description": "Description de l'extension incluse par 'manifest.json'." + }, + "labelFileBackup": { + "message": "Sauvegarde", + "description": "étiquette incluse dans le nom de fichier par défaut de JSON Sauvegardes générées par '@popup/store'." + }, + "labelAppVersion": { + "message": "Version", + "description": "Libellé de la version d'extension en cours affichée par '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Nom de Cloudflare Fournisseur affiché par '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Nom de hCaptcha Fournisseur affiché par '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Obtenir plus de passes!", + "description": "Texte mouseover affiché par '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sauvegarde de tous les passages", + "description": "Affiché par '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restaurer les passages à partir de la sauvegarde", + "description": "Affiché par '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Effacer toutes les passes", + "description": "Affiché par '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Afficher le Github", + "description": "Affiché par '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/fr_CA/messages.json b/public/_locales/fr_CA/messages.json new file mode 100644 index 00000000..f0e880a4 --- /dev/null +++ b/public/_locales/fr_CA/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nom de l'extension inclus par 'manifest.json', et affiché par '@popup/components/Header'." + }, + "appDescription": { + "message": "Support client pour Privacy Pass protocole d'autorisation anonyme.", + "description": "description de l'extension incluse par 'manifest.json'." + }, + "labelFileBackup": { + "message": "sauvegarde", + "description": "label inclus dans le nom de fichier par défaut JSON sauvegardes générées par '@popup/store'." + }, + "labelAppVersion": { + "message": "Version", + "description": "libellé de la version d'extension en cours affichée par '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nom de Cloudflare Fournisseur affiché par '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nom de hCaptcha Fournisseur affiché par '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Obtenir plus de passes!", + "description": "texte mouseover affiché par '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sauvegarde de toutes les passes", + "description": "affiché par '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restaurer les passes à partir de la sauvegarde", + "description": "affiché par '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Effacer toutes les passes", + "description": "affiché par '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Afficher sur Github", + "description": "affiché par '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ga/messages.json b/public/_locales/ga/messages.json new file mode 100644 index 00000000..2ee001a0 --- /dev/null +++ b/public/_locales/ga/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ainm an fhadaithe atá san áireamh 'manifest.json', agus ar taispeáint ag '@popup/components/Header'." + }, + "appDescription": { + "message": "Tacaíocht cliaint le haghaidh Privacy Pass @ info: inonyName.", + "description": "cur síos ar an síneadh san áireamh 'manifest.json'." + }, + "labelFileBackup": { + "message": "cúltaca", + "description": "lipéad ar áireamh i réamhainm réamhshocraithe JSON Gineadh an t-inneall '@popup/store'." + }, + "labelAppVersion": { + "message": "Leagan", + "description": "lipéid le leagan amach reatha a thaispeánfar ag '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ainm na Cloudflare soláthraí ar taispeáint ag '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ainm na hCaptcha soláthraí ar taispeáint ag '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Faigh pasanna níos mó!", + "description": "@ info: textnot need a translation '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Cúltaca Gach Pasanna", + "description": "taispeáint ag '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Athchóirigh Pasanna Ó Chúltaca", + "description": "taispeáint ag '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Glan Gach Pasanna", + "description": "taispeáint ag '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Amharc ar Github", + "description": "taispeáint ag '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/gu/messages.json b/public/_locales/gu/messages.json new file mode 100644 index 00000000..d5cf0e09 --- /dev/null +++ b/public/_locales/gu/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "એક્સટેન્સનો નામ દ્વારા સમાવેશ થાય છે 'manifest.json', અને પ્રદર્શિત '@popup/components/Header'." + }, + "appDescription": { + "message": "માટે ક્લાયન્ટ આધાર Privacy Pass અનામિક સત્તાધિકરણ પ્રોટોકોલ.", + "description": "એક્સટેન્સનો વર્ણન 'manifest.json'." + }, + "labelFileBackup": { + "message": "બેકઅપ", + "description": "લેબલ મૂળભૂત ફાઈલનામને સમાવેલ છે JSON દ્વારા પેદા થયેલ બેકઅપ '@popup/store'." + }, + "labelAppVersion": { + "message": "આવૃત્તિ", + "description": "વર્તમાન એક્સટેન્સન આવૃત્તિનું લેબલ પ્રદર્શિત '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "નું નામ Cloudflare પ્રદર્શિત પ્રદર્શિત '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "નું નામ hCaptcha પ્રદર્શિત પ્રદર્શિત '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "વધારે પાસ મેળવો!", + "description": "માઉસવેર લખાણ દ્વારા દર્શાવેલ છે '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "બધા પાસા બેકઅપ કરો", + "description": "પ્રદર્શિત '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "બેકઅપ માંથી પાસ પુનઃસંગ્રહો", + "description": "પ્રદર્શિત '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "બધા પાસા સાફ કરો", + "description": "પ્રદર્શિત '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "પર જુઓ Github", + "description": "પ્રદર્શિત '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/he/messages.json b/public/_locales/he/messages.json new file mode 100644 index 00000000..ff2f68a2 --- /dev/null +++ b/public/_locales/he/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "שם הרחבה הנכלל על ידי 'manifest.json', ומוצג על-ידי '@popup/components/Header'." + }, + "appDescription": { + "message": "תמיכת לקוח עבור Privacy Pass פרוטוקול אישור אנונימי.", + "description": "תיאור הרחבה שנכלל על ידי 'manifest.json'." + }, + "labelFileBackup": { + "message": "גיבוי", + "description": "התווית הכלול בשם ברירת המחדל של JSON גיבויים שנוצרו על-ידי '@popup/store'." + }, + "labelAppVersion": { + "message": "גרסה", + "description": "תווית של גרסת ההרחבה הנוכחית המוצגת על ידי '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "שם של Cloudflare ספק המוצג על ידי '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "שם של hCaptcha ספק המוצג על ידי '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "קבלת אישורים נוספים!", + "description": "תמליל mouseover המוצג על ידי '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "גיבוי כל הסיסמאות", + "description": "המוצג על ידי '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "שחזור סיסמאות מתוך גיבוי", + "description": "המוצג על ידי '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "ניקוי כל המעבר", + "description": "המוצג על ידי '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "הצגה ב - Github", + "description": "המוצג על ידי '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/hi/messages.json b/public/_locales/hi/messages.json new file mode 100644 index 00000000..aef4099c --- /dev/null +++ b/public/_locales/hi/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "द्वारा शामिल विस्तार का नाम 'manifest.json', द्वारा प्रदर्शित '@popup/components/Header'." + }, + "appDescription": { + "message": "क्लाइंट समर्थन के लिए Privacy Pass अनाम प्राधिकरण प्रोटोकॉल.", + "description": "द्वारा शामिल विस्तार का विवरण 'manifest.json'." + }, + "labelFileBackup": { + "message": "बैकअप", + "description": "के डिफ़ॉल्ट फ़ाइलनाम में लेबल शामिल है JSON द्वारा उत्पन्न बैकअप '@popup/store'." + }, + "labelAppVersion": { + "message": "संस्करण", + "description": "के द्वारा प्रदर्शित वर्तमान एक्सटेंशन संस्करण का लेबल '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "का नाम Cloudflare द्वारा प्रदर्शित '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "का नाम hCaptcha द्वारा प्रदर्शित '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "अधिक पास प्राप्त करें!", + "description": "द्वारा प्रदर्शित किया जा रहा है '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "सभी बैकअप को बैकअप करें", + "description": "द्वारा प्रदर्शित '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "बैकअप से फिर भंडारित करें (R)", + "description": "द्वारा प्रदर्शित '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "सभी पास साफ करें", + "description": "द्वारा प्रदर्शित '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "पर देखें Github", + "description": "द्वारा प्रदर्शित '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/hr/messages.json b/public/_locales/hr/messages.json new file mode 100644 index 00000000..8df35775 --- /dev/null +++ b/public/_locales/hr/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ime ekstenzije uključeno u 'manifest.json', i prikazuje '@popup/components/Header'." + }, + "appDescription": { + "message": "Podrška klijenta za Privacy Pass anonimni protokol autorizacije.", + "description": "opis proširenja uključenih u 'manifest.json'." + }, + "labelFileBackup": { + "message": "sigurnosno kopiranje", + "description": "oznaka uključena u default ime datoteke JSON sigurnosne kopije generirane po '@popup/store'." + }, + "labelAppVersion": { + "message": "Inačica", + "description": "oznaka trenutne verzije proširenja koja je prikazana '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ime Cloudflare pružatelj usluge prikazan '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ime hCaptcha pružatelj usluge prikazan '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Dobiti više prolaza!", + "description": "tekst za mouseover prikazan s '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sigurnosno Kopiranje Svih Propusnica", + "description": "prikazan po '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Vraćanje Prolaza Iz Sigurnosne Kopije", + "description": "prikazan po '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Očisti Sve Propusnice", + "description": "prikazan po '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Pogled na Github", + "description": "prikazan po '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/hu/messages.json b/public/_locales/hu/messages.json new file mode 100644 index 00000000..a75241dd --- /dev/null +++ b/public/_locales/hu/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "A által tartalmazott kiterjesztés neve 'manifest.json', és megjelenítve '@popup/components/Header'." + }, + "appDescription": { + "message": "Ügyfél támogatás Privacy Pass Anonim jogosultsági protokoll.", + "description": "A által tartalmazott kiterjesztés leírása 'manifest.json'." + }, + "labelFileBackup": { + "message": "Mentés", + "description": "Az alapértelmezett fájlnévben szereplő címke JSON által előállított mentések '@popup/store'." + }, + "labelAppVersion": { + "message": "Változat", + "description": "Az által megjelenített aktuális kiterjesztési változat címkéje '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "A neve Cloudflare Szolgáltató által megjelenített '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "A neve hCaptcha Szolgáltató által megjelenített '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Szerezz több belépőt!", + "description": "Szöveg által megjelenített szöveg '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Összes tartalék mentése", + "description": "Megjelenítve: '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Mentési osztályok visszaállítása", + "description": "Megjelenítve: '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Összes tábla törlése", + "description": "Megjelenítve: '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Nézet be Github", + "description": "Megjelenítve: '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/id/messages.json b/public/_locales/id/messages.json new file mode 100644 index 00000000..c6c62198 --- /dev/null +++ b/public/_locales/id/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nama dari ekstensi yang dimasukkan oleh 'manifest.json', dan ditampilkan oleh '@popup/components/Header'." + }, + "appDescription": { + "message": "Dukungan Klien untuk Privacy Pass protokol otorisasi anonim.", + "description": "deskripsi dari ekstensi yang disertakan oleh 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "label termasuk dalam nama berkas baku dari JSON Backup yang dihasilkan oleh '@popup/store'." + }, + "labelAppVersion": { + "message": "Versi", + "description": "label dari versi ekstensi saat ini ditampilkan oleh '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nama dari Cloudflare penyedia yang ditampilkan oleh '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nama dari hCaptcha penyedia yang ditampilkan oleh '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Dapatkan lebih banyak berlalu!", + "description": "teks mouseover yang ditampilkan oleh '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup Semua Melewati", + "description": "Ditampilkan oleh '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Pulihkan Operan Dari Backup", + "description": "Ditampilkan oleh '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Bersihkan Semua Lewat", + "description": "Ditampilkan oleh '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Tampilkan pada Github", + "description": "Ditampilkan oleh '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/it/messages.json b/public/_locales/it/messages.json new file mode 100644 index 00000000..29d8e368 --- /dev/null +++ b/public/_locales/it/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nome di estensione incluso da 'manifest.json', e visualizzati da '@popup/components/Header'." + }, + "appDescription": { + "message": "Supporto client per Privacy Pass protocollo di autorizzazione anonimo.", + "description": "descrizione dell'estensione inclusa da 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "etichetta inclusa nel nome file predefinito di JSON backup generati da '@popup/store'." + }, + "labelAppVersion": { + "message": "Versione", + "description": "etichetta della versione di estensione corrente visualizzata da '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nome di Cloudflare provider visualizzato da '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nome di hCaptcha provider visualizzato da '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Ricevi più passaggi!", + "description": "mouseover testo visualizzato da '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup Tutti I Passaggi", + "description": "visualizzato da '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restore Passa Da Backup", + "description": "visualizzato da '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Chiara Tutti I Passaggi", + "description": "visualizzato da '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vista su Github", + "description": "visualizzato da '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ja/messages.json b/public/_locales/ja/messages.json new file mode 100644 index 00000000..e7ff8251 --- /dev/null +++ b/public/_locales/ja/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "含まれる拡張の名前 'manifest.json', で表示される '@popup/components/Header'." + }, + "appDescription": { + "message": "クライアント・サポート Privacy Pass 匿名許可プロトコル.", + "description": "含まれる拡張の説明 'manifest.json'." + }, + "labelFileBackup": { + "message": "バックアップ", + "description": "デフォルトのファイル名に含まれるラベル JSON バックアップの生成者 '@popup/store'." + }, + "labelAppVersion": { + "message": "バージョン", + "description": "表示される現行拡張バージョンのラベル '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "の名前 Cloudflare 表示されるプロバイダー '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "の名前 hCaptcha 表示されるプロバイダー '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "パスをさらに取得!", + "description": "表示されるムーズオーバー・テキスト '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "すべてのパスのバックアップ", + "description": "表示者 '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "バックアップからのパスの復元", + "description": "表示者 '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "すべてのパスをクリア", + "description": "表示者 '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "表示 Github", + "description": "表示者 '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ko/messages.json b/public/_locales/ko/messages.json new file mode 100644 index 00000000..1875342d --- /dev/null +++ b/public/_locales/ko/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "포함된 확장자의 이름 'manifest.json', 로 표시되고 '@popup/components/Header'." + }, + "appDescription": { + "message": "클라이언트 지원 Privacy Pass 익명의 권한 부여 프로토콜.", + "description": "에 의해 포함된 확장에 대한 설명 'manifest.json'." + }, + "labelFileBackup": { + "message": "백업", + "description": "기본 파일 이름에 레이블이 포함되어 있습니다. JSON 백업에 의해 생성된 백업 '@popup/store'." + }, + "labelAppVersion": { + "message": "버전", + "description": "표시된 현재 확장 버전의 레이블 '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "이름 Cloudflare 공급자에 의해 표시되는 '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "이름 hCaptcha 공급자에 의해 표시되는 '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "더 많은 패스 가져오기!", + "description": "다음으로 표시되는 마우스오버 텍스트 '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "모든 패스 백업", + "description": "표시된 '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "백업으로부터 풀 복원", + "description": "표시된 '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "모든 패스 지우기", + "description": "표시된 '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "보기 Github", + "description": "표시된 '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/lt/messages.json b/public/_locales/lt/messages.json new file mode 100644 index 00000000..0a7fb17c --- /dev/null +++ b/public/_locales/lt/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "išplėtimo pavadinimas, kurį sudaro: 'manifest.json', ir rodoma: '@popup/components/Header'." + }, + "appDescription": { + "message": "Kliento parama Privacy Pass anoniminio leidimo protokolas.", + "description": "išplėtimas, kurį apima: 'manifest.json'." + }, + "labelFileBackup": { + "message": "atsarginė kopija", + "description": "etiketė, įtraukta į numatytąjį failo pavadinimą JSON atsargines kopijas, sukurtas pagal '@popup/store'." + }, + "labelAppVersion": { + "message": "Versija", + "description": "Esamos plėtinės versijos ženklas, matomas '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "pavadinimas Cloudflare Paslaugų teikėjas: '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "pavadinimas hCaptcha Paslaugų teikėjas: '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Gaukite daugiau leidimų!", + "description": "mouseover tekstas, rodomas '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Atsarginės kopijos visi praėjimai", + "description": "rodomi pagal '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Atstatyti pravažiavimas iš atsarginės kopijos", + "description": "rodomi pagal '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Išvalyti Visus Leidimus", + "description": "rodomi pagal '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vaizdas į Github", + "description": "rodomi pagal '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/lv/messages.json b/public/_locales/lv/messages.json new file mode 100644 index 00000000..9e325a72 --- /dev/null +++ b/public/_locales/lv/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "paplašinājuma nosaukums, ko iekļauj 'manifest.json', , un parāda '@popup/components/Header'." + }, + "appDescription": { + "message": "Klientu atbalsts Privacy Pass anonīms atļaujas protokols.", + "description": "paplašinājuma apraksts, kas iekļauts 'manifest.json'." + }, + "labelFileBackup": { + "message": "Dublējums", + "description": "Noklusētā faila nosaukums JSON , ko ģenerē '@popup/store'." + }, + "labelAppVersion": { + "message": "Versija", + "description": "pašreizējā paplašinājuma versija, ko attēlo '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "vārds, uzvārds Cloudflare Pakalpojumu sniedzējs '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "vārds, uzvārds hCaptcha Pakalpojumu sniedzējs '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Iegūt vairāk caurlaides!", + "description": "iemusever teksts, ko attēlo '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Rezerves kopēšana", + "description": ", kas '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Atjaunot Caurlaides No Rezerves", + "description": ", kas '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Tīrīt visas caurlaides", + "description": ", kas '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Skats uz Github", + "description": ", kas '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ml/messages.json b/public/_locales/ml/messages.json new file mode 100644 index 00000000..3d6a5e6b --- /dev/null +++ b/public/_locales/ml/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "എക്സ്റ്റെന്ഷനിന്റെ പേരു് 'manifest.json', പ്രദര്ശിപ്പിക്കുക '@popup/components/Header'." + }, + "appDescription": { + "message": "ക്ലൈന്റ് പിന്തുണ Privacy Pass അജ്ഞാതമായ അധികാരം പ്രോട്ടോക്കോൾ.", + "description": "എക്സ്റ്റൻഷനുകൾ ഉൾപ്പെടുത്തിയുള്ള വിശദീകരണം 'manifest.json'." + }, + "labelFileBackup": { + "message": "ബാക്കപ്പ്", + "description": "ലേബലിന്റെ സ്വതവേയുള്ള ഫയലിന്റെ പേരു് JSON ബാക്ക്പ് സൃഷ്ടിക്കപ്പെട്ടിരിക്കുന്നു '@popup/store'." + }, + "labelAppVersion": { + "message": "പതിപ്പ്", + "description": "നിലവിലുള്ള എക്സ്റ്റെന്ഷന് പതിപ്പിന്റെ ലേബല് '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "പേരു് Cloudflare പ്രദര്ശിപ്പിച്ച പ്രദര്ശനം '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "പേരു് hCaptcha പ്രദര്ശിപ്പിച്ച പ്രദര്ശനം '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "കൂടുതല് കടന്നുപോകൂ!", + "description": "പ്രദര്ശിപ്പിക്കുന്ന പദാവലി '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "എല്ലാ പാസ്സുകളെയും ബാക്കപ്പ് ചെയ്യുക", + "description": "പ്രദര്ശിപ്പിക്കുക '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "ബാക്കപ്പ് മുതല് പാസ്സുകള് വീണ്ടെടുക്കുക", + "description": "പ്രദര്ശിപ്പിക്കുക '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "എല്ലാ പാസ്സും വൃത്തിയാക്കുക", + "description": "പ്രദര്ശിപ്പിക്കുക '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "കാണുക Github", + "description": "പ്രദര്ശിപ്പിക്കുക '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ms/messages.json b/public/_locales/ms/messages.json new file mode 100644 index 00000000..7d93fa2e --- /dev/null +++ b/public/_locales/ms/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nama sambungan disertakan dengan 'manifest.json', dan dipaparkan oleh '@popup/components/Header'." + }, + "appDescription": { + "message": "Sokongan klien untuk Privacy Pass protokol keizinan tanpa nama.", + "description": "keterangan sambungan disertakan dengan 'manifest.json'." + }, + "labelFileBackup": { + "message": "sandaran", + "description": "label disertakan dalam nama fail lalai JSON backups dijana oleh '@popup/store'." + }, + "labelAppVersion": { + "message": "Versi", + "description": "label bagi versi sambungan semasa dipaparkan oleh '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nama bagi Cloudflare penyedia dipaparkan oleh '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nama bagi hCaptcha penyedia dipaparkan oleh '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Dapatkan lebih banyak pas!", + "description": "teks searah dipaparkan oleh '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sandar Semua Laluan", + "description": "dipapar oleh '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Pulih Semula Dari Bantuan", + "description": "dipapar oleh '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Kosongkan Semua Laluan", + "description": "dipapar oleh '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Lihat pada Github", + "description": "dipapar oleh '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/mt/messages.json b/public/_locales/mt/messages.json new file mode 100644 index 00000000..bc7c983d --- /dev/null +++ b/public/_locales/mt/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "isem ta ' estensjoni inkluż minn 'manifest.json', u murija minn '@popup/components/Header'." + }, + "appDescription": { + "message": "Klijent appoġġ għall Privacy Pass protokoll awtorizzazzjoni anonima.", + "description": "deskrizzjoni ta ' estensjoni inkluża minn 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "tikketta inkluża fil-filename default ta ' JSON backups iġġenerat minn '@popup/store'." + }, + "labelAppVersion": { + "message": "Verżjoni", + "description": "tikketta tal-verżjoni tal-estensjoni attwali murija minn '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "isem ta ' Cloudflare fornitur muri minn '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "isem ta ' hCaptcha fornitur muri minn '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Ikseb aktar tgħaddi!", + "description": "test mousever murija minn '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup Kollha Jgħaddi", + "description": "murija minn '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Jirrestawraw Jgħaddi Minn Backup", + "description": "murija minn '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Clear All Passe", + "description": "murija minn '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "View on Github", + "description": "murija minn '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/nb/messages.json b/public/_locales/nb/messages.json new file mode 100644 index 00000000..af1dc0a4 --- /dev/null +++ b/public/_locales/nb/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "navn på utvidelse inkludert av 'manifest.json', og vises av '@popup/components/Header'." + }, + "appDescription": { + "message": "Klientstøtte for Privacy Pass anonym autorisasjonsprotokoll.", + "description": "beskrivelse av utvidelse inkludert av 'manifest.json'." + }, + "labelFileBackup": { + "message": "sikkerhetskopiering", + "description": "etikett inkludert i standard filnavn for JSON sikkerhetskopier generert av '@popup/store'." + }, + "labelAppVersion": { + "message": "Versjon", + "description": "etikett av gjeldende utvidelsesversjon vist av '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "navn på Cloudflare leverandør vist av '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "navn på hCaptcha leverandør vist av '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Få flere pasninger!", + "description": "mouseover tekst vist av '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sikkerhetskopier alle passeringer", + "description": "vist av '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Gjenopprett pasninger fra sikkerhetskopi", + "description": "vist av '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Tøm alle passeringer", + "description": "vist av '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vis på Github", + "description": "vist av '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ne/messages.json b/public/_locales/ne/messages.json new file mode 100644 index 00000000..df5d83d2 --- /dev/null +++ b/public/_locales/ne/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "द्वारा समावेश गरिएको विस्तारको नाम 'manifest.json', र द्वारा प्रदर्शित '@popup/components/Header'." + }, + "appDescription": { + "message": "लागि क्लाइन्ट समर्थन Privacy Pass गुमनाम प्राधिकरण प्रोटोकल.", + "description": "द्वारा समावेश गरिएको विस्तारको वर्णन 'manifest.json'." + }, + "labelFileBackup": { + "message": "जगेडा", + "description": "को पूर्वनिर्धारित फाइलनाममा लेबुल समावेश JSON द्वारा उत्पन्न ब्याकअपहरू '@popup/store'." + }, + "labelAppVersion": { + "message": "संस्करण", + "description": "द्वारा प्रदर्शित वर्तमान विस्तार संस्करणको लेबल '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "नाम Cloudflare द्वारा प्रदर्शित प्रदायक '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "नाम hCaptcha द्वारा प्रदर्शित प्रदायक '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "धेरै पास प्राप्त गर्नुहोस्!", + "description": "द्वारा प्रदर्शित माउस पाठ '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "सबै पासहरू जगेडा", + "description": "द्वारा प्रदर्शित '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "ब्याकअपबाट पासहरू पूर्वावस्थामा ल्याउनुहोस्", + "description": "द्वारा प्रदर्शित '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "सबै पासहरू खाली गर्नुहोस्", + "description": "द्वारा प्रदर्शित '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "मा हेर्नुहोस् Github", + "description": "द्वारा प्रदर्शित '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/nl/messages.json b/public/_locales/nl/messages.json new file mode 100644 index 00000000..aa9f0110 --- /dev/null +++ b/public/_locales/nl/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "naam van de uitbreiding, inclusief 'manifest.json', en wordt afgebeeld door '@popup/components/Header'." + }, + "appDescription": { + "message": "Clientondersteuning voor Privacy Pass anoniem machtigingsprotocol.", + "description": "beschrijving van de uitbreiding, inclusief 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup maken", + "description": "label in standaard bestandsnaam van JSON backups gegenereerd door '@popup/store'." + }, + "labelAppVersion": { + "message": "Versie", + "description": "label van huidige uitbreidingsversie weergegeven door '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "naam van Cloudflare provider afgebeeld door '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "naam van hCaptcha provider afgebeeld door '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Meer pasjes!", + "description": "Mouseover-tekst wordt afgebeeld door '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup van alle passen", + "description": "afgebeeld door '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Terugzetdoorloopt vanaf backup", + "description": "afgebeeld door '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Alle passen wissen", + "description": "afgebeeld door '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Bekijken op Github", + "description": "afgebeeld door '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/pl/messages.json b/public/_locales/pl/messages.json new file mode 100644 index 00000000..39661e37 --- /dev/null +++ b/public/_locales/pl/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Nazwa rozszerzenia dołączonego przez 'manifest.json', i wyświetlane przez '@popup/components/Header'." + }, + "appDescription": { + "message": "Obsługa klienta dla Privacy Pass anonimowy protokół autoryzacji.", + "description": "opis rozszerzenia dołączonego przez 'manifest.json'." + }, + "labelFileBackup": { + "message": "kopia zapasowa", + "description": "etykieta dołączona do domyślnej nazwy pliku JSON kopie zapasowe utworzone przez '@popup/store'." + }, + "labelAppVersion": { + "message": "Wersja", + "description": "etykieta bieżącej wersji rozszerzenia wyświetlana przez '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nazwa Cloudflare Dostawca wyświetlany przez '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nazwa hCaptcha Dostawca wyświetlany przez '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Pobierz więcej wejściów!", + "description": "tekst wyświetlany przez użytkownika mouseover '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Wszystkie wejściówki kopii zapasowej", + "description": "wyświetlane przez '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Odtwórz kopie zapasowe z kopii zapasowej", + "description": "wyświetlane przez '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Wyczyść wszystkie wejściówki", + "description": "wyświetlane przez '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Wyświetl na Github", + "description": "wyświetlane przez '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/pt/messages.json b/public/_locales/pt/messages.json new file mode 100644 index 00000000..5a7137f7 --- /dev/null +++ b/public/_locales/pt/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "nome de extensão incluído por 'manifest.json', e exibido por '@popup/components/Header'." + }, + "appDescription": { + "message": "Suporte ao cliente para Privacy Pass protocolo de autorização de.", + "description": "descrição da extensão incluída por 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "rótulo incluído no arquivo padrão de arquivo de JSON backups gerados por '@popup/store'." + }, + "labelAppVersion": { + "message": "Versão", + "description": "rótulo de versão de extensão atual exibida por '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "nome de Cloudflare provedor exibido por '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "nome de hCaptcha provedor exibido por '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Obter mais passes!", + "description": "texto mouseover exibido por '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup Todos Os Passes", + "description": "exibido por '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restaurar Passes Do Backup", + "description": "exibido por '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Limpar Todos Os Passes", + "description": "exibido por '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Visualizar em Github", + "description": "exibido por '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ro/messages.json b/public/_locales/ro/messages.json new file mode 100644 index 00000000..e22f7c25 --- /dev/null +++ b/public/_locales/ro/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Denumirea de extensie inclusă de 'manifest.json', și afișată de '@popup/components/Header'." + }, + "appDescription": { + "message": "Suport client pentru Privacy Pass protocol de autorizare anonim.", + "description": "Descrierea extensiei incluse de 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "eticheta inclusă în numele de fișier implicit al JSON backup-uri generate de '@popup/store'." + }, + "labelAppVersion": { + "message": "Versiune", + "description": "eticheta de versiunea de extensie curentă afișată de '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Nume: Cloudflare furnizor afisat de '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Nume: hCaptcha furnizor afisat de '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Obține mai multe treceri!", + "description": "mouseover text afișat de '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Backup Toate Trecerile", + "description": "afişat de '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Restaurarea Trece De La Backup", + "description": "afişat de '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Șterge toate trecerile", + "description": "afişat de '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Vizualizare pe Github", + "description": "afişat de '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ru/messages.json b/public/_locales/ru/messages.json new file mode 100644 index 00000000..9db94e4e --- /dev/null +++ b/public/_locales/ru/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "имя расширения, включаное в 'manifest.json', и показанные '@popup/components/Header'." + }, + "appDescription": { + "message": "Поддержка клиента для Privacy Pass анонимный протокол авторизации.", + "description": "Описание расширения 'manifest.json'." + }, + "labelFileBackup": { + "message": "резервное копирование", + "description": "label включена в имя файла по умолчанию JSON резервные копии, созданные '@popup/store'." + }, + "labelAppVersion": { + "message": "Версия", + "description": "метка текущей версии расширения, отображаемого '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "имя Cloudflare провайдер, показанные '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "имя hCaptcha провайдер, показанные '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Получить больше пропусков!", + "description": "текст mouseover, отображаемый '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Резервное копирование всех пропусков", + "description": "показанные на '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Восстановить проходы из резервной копии", + "description": "показанные на '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Очистить все проходы", + "description": "показанные на '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Показать на Github", + "description": "показанные на '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/si/messages.json b/public/_locales/si/messages.json new file mode 100644 index 00000000..8454ad41 --- /dev/null +++ b/public/_locales/si/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "විසින් ඇතුලත් කරන ලද දිගුවක් 'manifest.json', විසින් පෙන්වයි. '@popup/components/Header'." + }, + "appDescription": { + "message": "සඳහා සහායක සහායක Privacy Pass නිර්නාමික අවසර පත්රය.", + "description": "විසින් ඇතුලත් කරන ලද දිගුවන විස්තරය 'manifest.json'." + }, + "labelFileBackup": { + "message": "උපස්ථ", + "description": "ලේබලය ඇතුලත් පෙරනිමි ගොනු නාමය JSON Name '@popup/store'." + }, + "labelAppVersion": { + "message": "සංස්කරණය", + "description": "විසින් පෙන්වන ලද වර්තමාන දිගහැරිම සංස්කරණය '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "නම Cloudflare විසින් පෙන්වන ලද සපයන්නා '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "නම hCaptcha විසින් පෙන්වන ලද සපයන්නා '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "තව පාස් වෙන්න.!", + "description": "විසින් ප්රදර්ශනය කරන ලද පෙළ '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "සියළුම මගපෙන්වීම", + "description": "විසින් පෙන්විය '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "බැකප් වලින් පාස් නැවත පිහිටුවන්න", + "description": "විසින් පෙන්විය '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "සියළු ගමන් මග පිරිසිදු කරන්න", + "description": "විසින් පෙන්විය '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "දසුන Github", + "description": "විසින් පෙන්විය '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/sk/messages.json b/public/_locales/sk/messages.json new file mode 100644 index 00000000..f4f9aecd --- /dev/null +++ b/public/_locales/sk/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "Názov rozšírenia, ktorý obsahuje 'manifest.json', a zobrazí '@popup/components/Header'." + }, + "appDescription": { + "message": "Podpora klientov pre Privacy Pass anonymný autorizačný.", + "description": "opis rozšírenia zahrnovaného 'manifest.json'." + }, + "labelFileBackup": { + "message": "záloha", + "description": "návestie zahrnuté do predvoleného názvu súboru JSON zálohy generované '@popup/store'." + }, + "labelAppVersion": { + "message": "Verzia", + "description": "návestie aktuálnej verzie rozšírenia, ktoré sa zobrazuje '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "názov Cloudflare poskytovateľ zobrazený '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "názov hCaptcha poskytovateľ zobrazený '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Získať viac priechodov!", + "description": "text, ktorý je zobrazený v ústí '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Zálohovať všetky prechody", + "description": "zobrazený podľa '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Obnoviť prechody zo zálohy", + "description": "zobrazený podľa '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Vyčistiť všetky prechody", + "description": "zobrazený podľa '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Zobraziť na Github", + "description": "zobrazený podľa '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/sl/messages.json b/public/_locales/sl/messages.json new file mode 100644 index 00000000..ca6f37a2 --- /dev/null +++ b/public/_locales/sl/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ime razširitve, vključeno v 'manifest.json', in se prikaže '@popup/components/Header'." + }, + "appDescription": { + "message": "Podpora za odjemalca za Privacy Pass anonimni protokol za.", + "description": "opis razširitve, ki jo vključuje 'manifest.json'." + }, + "labelFileBackup": { + "message": "varnostna kopija", + "description": "oznaka vključena v privzeto ime datoteke JSON varnostne kopije, ki jih generira '@popup/store'." + }, + "labelAppVersion": { + "message": "Različica", + "description": "oznaka trenutne različice razširitve, ki jo prikazuje '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ime Cloudflare ponudnik prikazal '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ime hCaptcha ponudnik prikazal '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Get več prepustnih!", + "description": "jezikast prikaz besedila, ki ga prikaže '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Varnostna Kopija Vseh Dovolilnice", + "description": "prikazan z '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Obnovi Prepustnice Iz Varnostne Kopije", + "description": "prikazan z '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Počistite Vse Prepustnice", + "description": "prikazan z '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Prikaži v Github", + "description": "prikazan z '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/sr/messages.json b/public/_locales/sr/messages.json new file mode 100644 index 00000000..2f0eb1a1 --- /dev/null +++ b/public/_locales/sr/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "име проширења укључено од стране 'manifest.json', и приказане '@popup/components/Header'." + }, + "appDescription": { + "message": "Подршка за клијента за Privacy Pass анонимни протокол ауторизације.", + "description": "опис проширења укључени од стране 'manifest.json'." + }, + "labelFileBackup": { + "message": "подршка", + "description": "ознака је подразумевано укључена у име фајла JSON Повратне копије генерисане '@popup/store'." + }, + "labelAppVersion": { + "message": "Верзија", + "description": "ознака за актуелну верзију проширења која је приказана '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "име Cloudflare добављач приказао '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "име hCaptcha добављач приказао '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Набавите више додавања!", + "description": "mусео преко текста приказано '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Бацкуп све пролази", + "description": "је приказано '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Врати пропуснице од резервне копије", + "description": "је приказано '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Обриши све пропуснице", + "description": "је приказано '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Прикажи на Github", + "description": "је приказано '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/sv/messages.json b/public/_locales/sv/messages.json new file mode 100644 index 00000000..575c994b --- /dev/null +++ b/public/_locales/sv/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "namn på utökningar som ingår i 'manifest.json', och visas av '@popup/components/Header'." + }, + "appDescription": { + "message": "Klientfunktioner för Privacy Pass anonymt behörighetsprotokoll.", + "description": "Beskrivning av den utökning som ingår i 'manifest.json'." + }, + "labelFileBackup": { + "message": "säkerhetskopiera", + "description": "etikett som ingår i standardfilnamnet för JSON säkerhetskopior som genereras av '@popup/store'." + }, + "labelAppVersion": { + "message": "Version", + "description": "Etikett för aktuell utökningsversion som visas av '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "Namn på Cloudflare leverantör som visas av '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "Namn på hCaptcha leverantör som visas av '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Få fler passningar!", + "description": "mouseoverstext som visas av '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Säkerhetskopiera alla Passes", + "description": "visas av '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Återställ Passes från backup", + "description": "visas av '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Rensa alla Passes", + "description": "visas av '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Visa på Github", + "description": "visas av '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ta/messages.json b/public/_locales/ta/messages.json new file mode 100644 index 00000000..209d0ad1 --- /dev/null +++ b/public/_locales/ta/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "விரிவாக்கத்தின் பெயர் 'manifest.json', & மூலம் தெரியும் '@popup/components/Header'." + }, + "appDescription": { + "message": "உறுப்பினர் ஆதரவு Privacy Pass அநாமதேய அதிகார நெறிமுறை.", + "description": "விரிவாக்கத்தின் விவரம் 'manifest.json'." + }, + "labelFileBackup": { + "message": "காப்பு", + "description": "லேபல் முன்னிருப்பு கோப்புப் பெயரில் சேர்க்கப்பட்டது JSON @ info: status '@popup/store'." + }, + "labelAppVersion": { + "message": "பதிப்பு", + "description": "தற்போதைய விரிவான பதிப்பின் விளக்கம் '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "பெயரை Cloudflare வழங்குபவர் '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "பெயரை hCaptcha வழங்குபவர் '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "அதிக கடவுச்சொற்கள்!", + "description": "முழுமையான உரை மூலம் தெரியும் '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "எல்லா கடவுச்சொல்", + "description": "Name '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "பின்புலிருந்து கடவுச்சொல் மீட்கவும்", + "description": "Name '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "அனைத்து வாசிகளையும் துடைக்க", + "description": "Name '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "காட்சி Github", + "description": "Name '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/te/messages.json b/public/_locales/te/messages.json new file mode 100644 index 00000000..1b4511f6 --- /dev/null +++ b/public/_locales/te/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ఎక్స్టెన్షన్ పేరును 'manifest.json', మరియు ప్రదర్శించబడినది '@popup/components/Header'." + }, + "appDescription": { + "message": "క్లయింట్ మద్దతుName Privacy Pass అనామక అధికారిత ప్రోటోకాల్.", + "description": "విస్తీర్ణము యొక్క వివరణ 'manifest.json'." + }, + "labelFileBackup": { + "message": "బ్యాకప్", + "description": "లేబుల్ అప్రమేయ ఫైల్ పేరు JSON Name '@popup/store'." + }, + "labelAppVersion": { + "message": "వెర్షన్", + "description": "ద్వారా ప్రదర్శించబడిన ప్రస్తుత విస్తరణ వెర్షన్ యొక్క లేబుల్ '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "పేరు Cloudflare ప్రొవైడర్ ద్వారా ప్రదర్శించబడినది '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "పేరు hCaptcha ప్రొవైడర్ ద్వారా ప్రదర్శించబడినది '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "ఎక్కువ పాస్ పొందండి!", + "description": "వున్న వచనమును చూపుము '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "బ్యాక్అప్ అన్ని పాస్", + "description": "ప్రదర్శించబడినది '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "బ్యాక్అప్ నుండి పాస్ ను పునఃప్రారంభించుము", + "description": "ప్రదర్శించబడినది '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "అన్ని పాస్ లను శుభ్రముచేయుము", + "description": "ప్రదర్శించబడినది '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "వీక్షణం Github", + "description": "ప్రదర్శించబడినది '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/th/messages.json b/public/_locales/th/messages.json new file mode 100644 index 00000000..189566d2 --- /dev/null +++ b/public/_locales/th/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "ชื่อของส่วนขยายที่รวมไว้โดย 'manifest.json', และแสดงโดย '@popup/components/Header'." + }, + "appDescription": { + "message": "การสนับสนุนไคลเอ็นต์สำหรับ Privacy Pass โปรโตคอลการพิสูจน์ตัวตน.", + "description": "รายละเอียดของส่วนขยายที่รวมโดย 'manifest.json'." + }, + "labelFileBackup": { + "message": "สำรองข้อมูล", + "description": "เลเบลรวมอยู่ในชื่อไฟล์ดีฟอลต์ของ JSON สำรองข้อมูลที่สร้างโดย '@popup/store'." + }, + "labelAppVersion": { + "message": "เวอร์ชัน", + "description": "เลเบลของเวอร์ชันส่วนขยายปัจจุบันที่แสดงโดย '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ชื่อของ Cloudflare ผู้ให้บริการที่แสดงโดย '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ชื่อของ hCaptcha ผู้ให้บริการที่แสดงโดย '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "รับบัตรผ่าน!", + "description": "Mouse over ข้อความที่แสดงโดย '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "สำรองข้อมูลผ่านทั้งหมด", + "description": "แสดงโดย '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "เรียกคืนผ่านจากสำเนาสำรอง", + "description": "แสดงโดย '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "ล้างรหัสผ่านทั้งหมด", + "description": "แสดงโดย '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "ดูบน Github", + "description": "แสดงโดย '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/tr/messages.json b/public/_locales/tr/messages.json new file mode 100644 index 00000000..08682965 --- /dev/null +++ b/public/_locales/tr/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "buna dahil olan uzantı adı 'manifest.json', ve görüntülenen '@popup/components/Header'." + }, + "appDescription": { + "message": "İstemci desteği Privacy Pass anonim yetkilendirme iletişim kuralı.", + "description": "içinde yer alan uzantının açıklaması 'manifest.json'." + }, + "labelFileBackup": { + "message": "yedekleme", + "description": "varsayılan dosya adı olan etiket JSON tarafından oluşturulan yedeklemeler '@popup/store'." + }, + "labelAppVersion": { + "message": "Sürüm", + "description": "Görüntülenen yürürlükteki uzantı sürümünün etiketi: '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "adı Cloudflare sağlayıcı tarafından görüntülenen '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "adı hCaptcha sağlayıcı tarafından görüntülenen '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Daha fazla pas al!", + "description": "fare tarafından görüntülenen mouseover metni '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Tüm Geçişleri Yedekle", + "description": "görüntülenen '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Yedekten Geçişleri Geri Yükle", + "description": "görüntülenen '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Tüm Geçişleri Temizle", + "description": "görüntülenen '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Görüntüle: Github", + "description": "görüntülenen '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/uk/messages.json b/public/_locales/uk/messages.json new file mode 100644 index 00000000..3642a1f5 --- /dev/null +++ b/public/_locales/uk/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "назва розширення, включено до 'manifest.json', і буде показано '@popup/components/Header'." + }, + "appDescription": { + "message": "Підтримка клієнта Privacy Pass анонімний протокол розпізнавання.", + "description": "опис суфікса 'manifest.json'." + }, + "labelFileBackup": { + "message": "backup", + "description": "мітка з міткою, включено до типового файла JSON створення резервних копій '@popup/store'." + }, + "labelAppVersion": { + "message": "Версія", + "description": "мітка поточної версії розширення, яку буде показано '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "ім ' я Cloudflare Постачальник показу '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "ім ' я hCaptcha Постачальник показу '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Отримати більше пропусків!", + "description": "mouseext text, показаних '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Резервувати всі пропуски", + "description": "Відображено '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Відновити пропуски з резервної копії", + "description": "Відображено '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Очистити всі пропуски", + "description": "Відображено '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Переглянути Github", + "description": "Відображено '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/ur/messages.json b/public/_locales/ur/messages.json new file mode 100644 index 00000000..609dcda8 --- /dev/null +++ b/public/_locales/ur/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "توسیع کے نام پر شامل 'manifest.json', اور دکھایا. '@popup/components/Header'." + }, + "appDescription": { + "message": "کے لئے کلائنٹ کی حمایت Privacy Pass گمنام اجازت نامہ.", + "description": "توسیع کی تفصیل شامل 'manifest.json'." + }, + "labelFileBackup": { + "message": "بیک اپ", + "description": "کا لیبل پہلے سے طے شدہ فائل میں شامل JSON کی طرف سے پیدا بیک اپ '@popup/store'." + }, + "labelAppVersion": { + "message": "ورژن", + "description": "موجودہ توسیع شدہ ورژن کا لیبل لگا کر آویزاں کریں '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "کا نام Cloudflare فراہم کردہ فراہم کار '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "کا نام hCaptcha فراہم کردہ فراہم کار '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "مزید گزر جاؤ!", + "description": "Mouseover کے ٹیکسٹ کی نمائش '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "بیک اپ تمام پاسورڈ", + "description": "کی نمائش '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "بیک اپ سے ریمور پاسورڈ", + "description": "کی نمائش '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "واضح تمام پاسورڈ", + "description": "کی نمائش '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "دیکھیں Github", + "description": "کی نمائش '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/vi/messages.json b/public/_locales/vi/messages.json new file mode 100644 index 00000000..1270598d --- /dev/null +++ b/public/_locales/vi/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "tên phần mở rộng bao gồm bởi 'manifest.json', và hiển thị bởi '@popup/components/Header'." + }, + "appDescription": { + "message": "Hỗ trợ khách hàng cho Privacy Pass giao thức ủy quyền.", + "description": "mô tả phần mở rộng bao gồm 'manifest.json'." + }, + "labelFileBackup": { + "message": "sao lưu", + "description": "Nhãn bao gồm tên tập tin mặc định của JSON lưu lại sau khi tạo '@popup/store'." + }, + "labelAppVersion": { + "message": "Phiên bản", + "description": "nhãn phiên bản mở rộng hiện tại hiển thị bởi '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "tên của Cloudflare được hiển thị bởi '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "tên của hCaptcha được hiển thị bởi '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "Get more passes!", + "description": "Văn bản mouseover hiển thị bởi '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "Sao lưu tất cả", + "description": "hiển thị bởi '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "Khôi Phục Thông Qua Sao Lưu", + "description": "hiển thị bởi '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "Xóa tất cả", + "description": "hiển thị bởi '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "Xem trên Github", + "description": "hiển thị bởi '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/zh/messages.json b/public/_locales/zh/messages.json new file mode 100644 index 00000000..2234e2db --- /dev/null +++ b/public/_locales/zh/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "扩展名所包含的扩展名的名称 'manifest.json', 并显示为 '@popup/components/Header'." + }, + "appDescription": { + "message": "客户机支持 Privacy Pass 匿名授权协议.", + "description": "扩展的描述 'manifest.json'." + }, + "labelFileBackup": { + "message": "备份", + "description": "缺省文件名中包含的标签 JSON 生成的备份 '@popup/store'." + }, + "labelAppVersion": { + "message": "版本", + "description": "当前扩展版本显示的标签 '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "名称 Cloudflare 提供者显示者 '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "名称 hCaptcha 提供者显示者 '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "获取更多通行证!", + "description": "显示的鼠标悬停文本 '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "备份所有传递", + "description": "显示的 '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "从备份恢复传递", + "description": "显示的 '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "清除所有传递", + "description": "显示的 '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "查看 Github", + "description": "显示的 '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/_locales/zh_TW/messages.json b/public/_locales/zh_TW/messages.json new file mode 100644 index 00000000..8f0b2a8a --- /dev/null +++ b/public/_locales/zh_TW/messages.json @@ -0,0 +1,46 @@ +{ + "appName": { + "message": "Privacy Pass", + "description": "包含的延伸名稱 'manifest.json', 並由顯示 '@popup/components/Header'." + }, + "appDescription": { + "message": "的用戶端支援 Privacy Pass 匿名授權通訊協定.", + "description": "說明的延伸說明 'manifest.json'." + }, + "labelFileBackup": { + "message": "備份", + "description": "預設檔名中包含的標籤 JSON 備份產生的 '@popup/store'." + }, + "labelAppVersion": { + "message": "版本", + "description": "顯示現行延伸版本的標籤 '@popup/components/Header'." + }, + "providerNameCloudflare": { + "message": "Cloudflare", + "description": "名 Cloudflare 顯示的提供者 '@popup/components/CloudflareButton'." + }, + "providerNameHcaptcha": { + "message": "hCaptcha", + "description": "名 hCaptcha 顯示的提供者 '@popup/components/HcaptchaButton'." + }, + "ctaGetMorePasses": { + "message": "取得更多通行證!", + "description": "顯示的 mouseover 文字 '@popup/components/PassButton'." + }, + "ctaBackupAllPasses": { + "message": "備份所有通道", + "description": "顯示者 '@popup/components/BackupButton'." + }, + "ctaRestorePasses": { + "message": "從備份還原傳遞", + "description": "顯示者 '@popup/components/RestoreButton'." + }, + "ctaClearAllPasses": { + "message": "清除所有傳遞", + "description": "顯示者 '@popup/components/ClearButton'." + }, + "ctaViewOnGithub": { + "message": "檢視於 Github", + "description": "顯示者 '@popup/components/GithubButton'." + } +} \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index 8570e770..172806b4 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.6.6", + "version": "3.7.0", "manifest_version": 2, "default_locale": "en", "icons": { From 6ee36f87a699c942034db5e0801f93fe3eed8568 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Wed, 23 Feb 2022 22:45:22 -0800 Subject: [PATCH 34/36] minor html/css tweak to popup window * each row of providers contains: name, token count - previously, 2x floating divs were used for layout * there were no css rules to properly display text overflow * when only english strings were displayed, this was acceptable * when other language translations are displayed, this goes wrong very quickly - now, a simple 1x row by 2x column table element is used for layout * though a flexbox layout would be perfectly suited, it's only supported by newer browsers * table elements have always existed, and it works equally well example: ======== * run Chrome using strings translated for German locale - bash: LANGUAGE='de' && chrome --lang=de - cmd in Windows: set "LANGUAGE=de" && chrome --lang=de --- package.json | 2 +- public/manifest.json | 2 +- src/popup/components/PassButton/index.tsx | 12 ++++++++++-- .../components/PassButton/styles.module.scss | 15 +++++++++------ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 46ada648..fc4fd012 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.7.0", + "version": "3.7.1", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 172806b4..6f21bfc8 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.7.0", + "version": "3.7.1", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/popup/components/PassButton/index.tsx b/src/popup/components/PassButton/index.tsx index 178f963a..6c64a83a 100644 --- a/src/popup/components/PassButton/index.tsx +++ b/src/popup/components/PassButton/index.tsx @@ -22,8 +22,16 @@ export function PassButton(props: Props): JSX.Element { onMouseEnter={onEnter} onMouseLeave={onLeave} > -
{element}
-
{props.value}
+ + + + + +
+ {element} + + {props.value} +
); } diff --git a/src/popup/components/PassButton/styles.module.scss b/src/popup/components/PassButton/styles.module.scss index 0b6f2b67..d14b6f26 100644 --- a/src/popup/components/PassButton/styles.module.scss +++ b/src/popup/components/PassButton/styles.module.scss @@ -10,17 +10,20 @@ $_font-weight: 200; background-color: colors.$light-grey; font-size: $_font-size; font-weight: $_font-weight; +} - white-space: nowrap; - line-height: 1em; - height: 1em; - clear: both; +.table { + width: 100%; + box-sizing: border-box; + border-collapse: collapse; } .content { - float: left; + width: 100%; + text-align: left; + padding-right: 1em; } .value { - float: right; + text-align: right; } From 333f32b25d06fcddca9dc0ab8f1ce37dc8c474b8 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 20 Mar 2022 00:09:22 -0700 Subject: [PATCH 35/36] accomodate for changes to Cloudflare provider backend previously, request to issue tokens required querystring parameter: __cf_chl_captcha_tk__ now, the name of this parameter has been changed to: __cf_chl_f_tk --- package.json | 2 +- public/manifest.json | 2 +- src/background/providers/cloudflare.test.ts | 10 ++++++---- src/background/providers/cloudflare.ts | 20 +++++++++++--------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index fc4fd012..ffa4b039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "privacy-pass", - "version": "3.7.1", + "version": "3.7.2", "private": true, "contributors": [ "Suphanat Chunhapanya ", diff --git a/public/manifest.json b/public/manifest.json index 6f21bfc8..b62830aa 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "description": "__MSG_appDescription__", - "version": "3.7.1", + "version": "3.7.2", "manifest_version": 2, "default_locale": "en", "icons": { diff --git a/src/background/providers/cloudflare.test.ts b/src/background/providers/cloudflare.test.ts index 177e9e26..14152510 100644 --- a/src/background/providers/cloudflare.test.ts +++ b/src/background/providers/cloudflare.test.ts @@ -102,7 +102,7 @@ describe('issuance', () => { const validDetails: chrome.webRequest.WebRequestBodyDetails = { method: 'POST', - url: 'https://captcha.website/?__cf_chl_captcha_tk__=query-param', + url: 'https://captcha.website/?__cf_chl_f_tk=query-param', requestId: 'xxx', frameId: 1, parentFrameId: 1, @@ -208,7 +208,7 @@ describe('issuance', () => { const headDetails: any = { ...validDetails, requestHeaders: [ - { name: "referer", value: validDetails.url.replace('__cf_chl_captcha_tk__', '__cf_chl_tk') }, + { name: "referer", value: validDetails.url.replace('__cf_chl_f_tk', '__cf_chl_tk') }, ], }; delete headDetails.requestBody; @@ -246,12 +246,14 @@ describe('issuance', () => { /* * The request is invalid if any of the followings is true: * 1. It has no url param of any of the followings: - * a. '__cf_chl_captcha_tk__' - * b. '__cf_chl_managed_tk__' + * a. '__cf_chl_f_tk' + * b. '__cf_chl_captcha_tk__' + * c. '__cf_chl_managed_tk__' * 2. It has no body param of any of the followings: * a. 'g-recaptcha-response' * b. 'h-captcha-response' * c. 'cf_captcha_kind' + * d. 'cf_ch_verify' */ test('invalid request: no query param', () => { diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index 7559a41e..cb40f3a8 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -6,11 +6,13 @@ import Token from '../token'; import axios from 'axios'; import qs from 'qs'; -const NUMBER_OF_REQUESTED_TOKENS: number = 30; -const DEFAULT_ISSUING_HOSTNAME: string = 'captcha.website'; -const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; -const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; -const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; +const NUMBER_OF_REQUESTED_TOKENS: number = 30; +const DEFAULT_ISSUING_HOSTNAME: string = 'captcha.website'; +const DEFAULT_ISSUING_QUERY_PARAM: string = '__cf_chl_f_tk'; +const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; +const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; +const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; +const ISSUANCE_REFERER_REGEX: RegExp = /^(.*[\?&])(?:__cf_chl_tk)([=].*)$/ig; const COMMITMENT_URL: string = 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; @@ -29,10 +31,10 @@ const ALL_ISSUING_CRITERIA: { exact : ['/'], }, QUERY_PARAMS: { - some: ['__cf_chl_captcha_tk__', '__cf_chl_managed_tk__'], + some: [DEFAULT_ISSUING_QUERY_PARAM, '__cf_chl_captcha_tk__', '__cf_chl_managed_tk__'], }, BODY_PARAMS: { - some: ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind'], + some: ['g-recaptcha-response', 'h-captcha-response', 'cf_captcha_kind', 'cf_ch_verify'], }, } @@ -280,8 +282,8 @@ export class CloudflareProvider extends Provider { if (details.requestHeaders) { const ref_header = details.requestHeaders.find(h => (h !== undefined) && h.name && h.value && (h.name.toLowerCase() === 'referer')); - if (ref_header !== undefined) { - href = ref_header.value!.replace(/([\?&])(?:__cf_chl_tk)([=])/ig, ('$1' + '__cf_chl_captcha_tk__' + '$2')); + if ((ref_header !== undefined) && (ref_header.value !== undefined) && ISSUANCE_REFERER_REGEX.test(ref_header.value)) { + href = ref_header.value.replace(ISSUANCE_REFERER_REGEX, ('$1' + DEFAULT_ISSUING_QUERY_PARAM + '$2')); url = new URL(href); // Only issue tokens when querystring parameters pass defined criteria. From ad517ddfc8ee3d5998e02acae2ddfb63e64eb521 Mon Sep 17 00:00:00 2001 From: Warren R Bank Date: Sun, 20 Mar 2022 00:51:50 -0700 Subject: [PATCH 36/36] CF: normalize name of querystring parameter in request to issue tokens the detection criteria allows for any of several parameter names, but the backend endpoint to issue Cloudflare tokens only accepts a single parameter name. --- src/background/providers/cloudflare.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/background/providers/cloudflare.ts b/src/background/providers/cloudflare.ts index cb40f3a8..6b0bfdfe 100644 --- a/src/background/providers/cloudflare.ts +++ b/src/background/providers/cloudflare.ts @@ -12,7 +12,6 @@ const DEFAULT_ISSUING_QUERY_PARAM: string = '__cf_chl_f_tk'; const CHL_BYPASS_SUPPORT: string = 'cf-chl-bypass'; const ISSUE_HEADER_NAME: string = 'cf-chl-bypass'; const ISSUANCE_BODY_PARAM_NAME: string = 'blinded-tokens'; -const ISSUANCE_REFERER_REGEX: RegExp = /^(.*[\?&])(?:__cf_chl_tk)([=].*)$/ig; const COMMITMENT_URL: string = 'https://raw.githubusercontent.com/privacypass/ec-commitments/master/commitments-p256.json'; @@ -38,6 +37,9 @@ const ALL_ISSUING_CRITERIA: { }, } +const ISSUANCE_REFERER_REGEX: RegExp = /^(.*[\?&])(?:__cf_chl_tk)([=].*)$/i; +const ISSUANCE_QUERY_PARAM_REGEX: RegExp = new RegExp('^(.*[\\?&])(?:' + ALL_ISSUING_CRITERIA.QUERY_PARAMS!.some!.join('|') + ')([=].*)$', 'i'); + const VERIFICATION_KEY: string = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExf0AftemLr0YSz5odoj3eJv6SkOF VcH7NNb2xwdEz6Pxm44tvovEl/E+si8hdIDVg1Ys+cbaWwP0jYJW3ygv+Q== @@ -274,23 +276,24 @@ export class CloudflareProvider extends Provider { href = this.issueInfo.url; url = new URL(href); - // Only issue tokens when querystring parameters pass defined criteria. + // Issue tokens when querystring parameters pass defined criteria. if (areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { + if (href.indexOf(DEFAULT_ISSUING_QUERY_PARAM) === -1) { + // Normalize name of querystring parameter + href = href.replace(ISSUANCE_QUERY_PARAM_REGEX, ('$1' + DEFAULT_ISSUING_QUERY_PARAM + '$2')); + this.issueInfo.url = href; + } return true; } + // Issue tokens when querystring parameters in value of 'Referer' request header pass defined criteria. if (details.requestHeaders) { const ref_header = details.requestHeaders.find(h => (h !== undefined) && h.name && h.value && (h.name.toLowerCase() === 'referer')); if ((ref_header !== undefined) && (ref_header.value !== undefined) && ISSUANCE_REFERER_REGEX.test(ref_header.value)) { href = ref_header.value.replace(ISSUANCE_REFERER_REGEX, ('$1' + DEFAULT_ISSUING_QUERY_PARAM + '$2')); - url = new URL(href); - - // Only issue tokens when querystring parameters pass defined criteria. - if (areQualifiedQueryParams(ALL_ISSUING_CRITERIA.QUERY_PARAMS, url)) { - this.issueInfo.url = href; - return true; - } + this.issueInfo.url = href; + return true; } }