diff --git a/.template-env b/.template-env index 686a8748ab..2e6fcf10d0 100644 --- a/.template-env +++ b/.template-env @@ -31,7 +31,6 @@ FORMSG_SDK_MODE= # APP_NAME=FormSG # OTP_LIFE_SPAN=900000 # BOUNCE_LIFE_SPAN=86400000 -# AGGREGATE_COLLECTION= # If provided, a banner with the provided message will show up in every form. # IS_GENERAL_MAINTENANCE= diff --git a/.vscode/settings.json b/.vscode/settings.json index 7702f5b168..8ad8525a2e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,6 @@ "source.fixAll.eslint": false }, "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d6e9791f..ca1d6e70ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,86 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v5.15.0](https://github.com/opengovsg/FormSG/compare/v5.14.1...v5.15.0) + +- feat: Add webhook support for storage mode attachments [`#1713`](https://github.com/opengovsg/FormSG/pull/1713) + +#### [v5.14.1](https://github.com/opengovsg/FormSG/compare/v5.14.0...v5.14.1) + +> 15 June 2021 + +- chore: bump version to v5.14.1 [`d7a612e`](https://github.com/opengovsg/FormSG/commit/d7a612e55ed56aac1d7784d25a8b894b924f3401) +- fix: allow read key in permissionList when updating collaborators [`6ae1af6`](https://github.com/opengovsg/FormSG/commit/6ae1af6859e8b9be275204f4274ba396a6769ff4) + +#### [v5.14.0](https://github.com/opengovsg/FormSG/compare/v5.13.1...v5.14.0) + +> 15 June 2021 + +- feat: revert backwards-incompatible changes for v5.14.0 [`#2169`](https://github.com/opengovsg/FormSG/pull/2169) +- fix(auth.client.controller): add missing undefinedness check [`#2168`](https://github.com/opengovsg/FormSG/pull/2168) +- refactor: simplify isUenValid logic [`#2156`](https://github.com/opengovsg/FormSG/pull/2156) +- fix(deps): bump @opengovsg/spcp-auth-client from 1.4.7 to 1.4.8 [`#2164`](https://github.com/opengovsg/FormSG/pull/2164) +- fix(deps): bump @sentry/integrations from 6.6.0 to 6.7.0 [`#2163`](https://github.com/opengovsg/FormSG/pull/2163) +- chore(deps-dev): bump @typescript-eslint/parser from 4.26.1 to 4.27.0 [`#2162`](https://github.com/opengovsg/FormSG/pull/2162) +- refactor(encrypt-submission): introduce IncomingEncryptSubmission [`#1987`](https://github.com/opengovsg/FormSG/pull/1987) +- chore(deps-dev): bump @typescript-eslint/eslint-plugin [`#2161`](https://github.com/opengovsg/FormSG/pull/2161) +- fix(deps): bump @sentry/browser from 6.6.0 to 6.7.0 [`#2160`](https://github.com/opengovsg/FormSG/pull/2160) +- refactor(angularjs): remove angular-moment [`#2154`](https://github.com/opengovsg/FormSG/pull/2154) +- feat: add UEN field [`#2100`](https://github.com/opengovsg/FormSG/pull/2100) +- build: mute Localstack logs [`#2146`](https://github.com/opengovsg/FormSG/pull/2146) +- feat(feature-manager): remove GoogleAnalytics feature [`#2127`](https://github.com/opengovsg/FormSG/pull/2127) +- feat(feature-manager): hardcode /features API response [`#2149`](https://github.com/opengovsg/FormSG/pull/2149) +- fix(deps): bump zod from 3.1.0 to 3.2.0 [`#2151`](https://github.com/opengovsg/FormSG/pull/2151) +- fix(deps): bump aws-sdk from 2.925.0 to 2.927.0 [`#2153`](https://github.com/opengovsg/FormSG/pull/2153) +- chore(deps-dev): bump @types/mongodb from 3.6.17 to 3.6.18 [`#2152`](https://github.com/opengovsg/FormSG/pull/2152) +- chore(deps-dev): bump htmlhint from 0.14.2 to 0.15.1 [`#2150`](https://github.com/opengovsg/FormSG/pull/2150) +- chore: merge v5.13.1 into develop [`#2144`](https://github.com/opengovsg/FormSG/pull/2144) +- docs(feature-manager): update docs to reflect FeatureManager removal [`#2145`](https://github.com/opengovsg/FormSG/pull/2145) +- feat(feature-manager): remove Sentry from feature manager [`#2130`](https://github.com/opengovsg/FormSG/pull/2130) +- fix(submissions): remove captcha dependence on feature toggle [`#2143`](https://github.com/opengovsg/FormSG/pull/2143) +- chore(deps-dev): bump @babel/plugin-transform-runtime [`#2141`](https://github.com/opengovsg/FormSG/pull/2141) +- fix(deps): bump aws-sdk from 2.924.0 to 2.925.0 [`#2136`](https://github.com/opengovsg/FormSG/pull/2136) +- fix(deps): bump @sentry/browser from 6.5.1 to 6.6.0 [`#2135`](https://github.com/opengovsg/FormSG/pull/2135) +- chore(deps-dev): bump @babel/preset-env from 7.14.4 to 7.14.5 [`#2140`](https://github.com/opengovsg/FormSG/pull/2140) +- fix(deps): bump @babel/runtime from 7.14.0 to 7.14.5 [`#2139`](https://github.com/opengovsg/FormSG/pull/2139) +- chore(deps-dev): bump @babel/core from 7.14.3 to 7.14.5 [`#2138`](https://github.com/opengovsg/FormSG/pull/2138) +- fix(deps): bump @sentry/integrations from 6.5.1 to 6.6.0 [`#2137`](https://github.com/opengovsg/FormSG/pull/2137) +- test(adminsubmissionservice): unit tests for download methods [`#2129`](https://github.com/opengovsg/FormSG/pull/2129) +- feat(feature-manager): remove Intranet from feature manager [`#2131`](https://github.com/opengovsg/FormSG/pull/2131) +- refactor(submissions.client.factory): refactored download methods [`#2054`](https://github.com/opengovsg/FormSG/pull/2054) +- refactor(auth.client.service): refactor to Typescript [`#2132`](https://github.com/opengovsg/FormSG/pull/2132) +- test: remove basic and full e2e test separation [`#2128`](https://github.com/opengovsg/FormSG/pull/2128) +- fix(auth): make login emails case-insensitive [`#2125`](https://github.com/opengovsg/FormSG/pull/2125) +- feat(feature-manager): tear out AggregateStats feature [`#2120`](https://github.com/opengovsg/FormSG/pull/2120) +- feat(feature-manager): remove feature toggles from frontend [`#2118`](https://github.com/opengovsg/FormSG/pull/2118) +- refactor: formFactoryClientService [`#2117`](https://github.com/opengovsg/FormSG/pull/2117) +- chore(deps-dev): bump csv-parse from 4.15.4 to 4.16.0 [`#2124`](https://github.com/opengovsg/FormSG/pull/2124) +- fix(deps): bump aws-sdk from 2.923.0 to 2.924.0 [`#2123`](https://github.com/opengovsg/FormSG/pull/2123) +- chore(deps-dev): bump @types/validator from 13.1.3 to 13.1.4 [`#2122`](https://github.com/opengovsg/FormSG/pull/2122) +- test: loosen URL check in e2e test [`#2119`](https://github.com/opengovsg/FormSG/pull/2119) +- feat: add and call v3 API for retrieving individual admin form [`#2113`](https://github.com/opengovsg/FormSG/pull/2113) +- chore: remove blocking of SP and RP admin updates [`#2114`](https://github.com/opengovsg/FormSG/pull/2114) +- refactor(auth.client): (1) extract email validation and send login otp flow to Typescript [`#2084`](https://github.com/opengovsg/FormSG/pull/2084) +- feat: store only user ID in session [`#1849`](https://github.com/opengovsg/FormSG/pull/1849) +- fix: return storage mode submission version when when retrieving from server [`#2112`](https://github.com/opengovsg/FormSG/pull/2112) +- refactor: replace $resource in angularjs form-api.client.factory.js with typescript FormService [`#1947`](https://github.com/opengovsg/FormSG/pull/1947) +- refactor(ts-migration): ndjsonstream and process-decrypted-content [`#2111`](https://github.com/opengovsg/FormSG/pull/2111) +- chore: remove redundant ValidationOption object properties for short text, long text and number fields [`#2040`](https://github.com/opengovsg/FormSG/pull/2040) +- chore(deps-dev): bump @types/node from 14.17.2 to 14.17.3 [`#2108`](https://github.com/opengovsg/FormSG/pull/2108) +- chore(deps-dev): bump ts-essentials from 7.0.1 to 7.0.2 [`#2110`](https://github.com/opengovsg/FormSG/pull/2110) +- chore(deps-dev): bump @types/express-rate-limit from 5.1.1 to 5.1.2 [`#2109`](https://github.com/opengovsg/FormSG/pull/2109) +- fix(deps): bump zod from 3.0.0 to 3.1.0 [`#2107`](https://github.com/opengovsg/FormSG/pull/2107) +- fix(deps): bump aws-sdk from 2.922.0 to 2.923.0 [`#2105`](https://github.com/opengovsg/FormSG/pull/2105) +- chore: merge release v5.13.0 into develop [`#2102`](https://github.com/opengovsg/FormSG/pull/2102) +- feat: Add webhook support for storage mode attachments [`#1713`](https://github.com/opengovsg/FormSG/pull/1713) +- refactor(auth.client.service): refactor to Typescript (#2132) [`#2066`](https://github.com/opengovsg/FormSG/issues/2066) +- chore: bump version to 5.14.0 [`a3f6291`](https://github.com/opengovsg/FormSG/commit/a3f6291e0800c18024b635344aafcddcd7c0a567) + #### [v5.13.1](https://github.com/opengovsg/FormSG/compare/v5.13.0...v5.13.1) +> 11 June 2021 + +- chore: bump version to 5.13.1 [`7c87bf3`](https://github.com/opengovsg/FormSG/commit/7c87bf3af16347b630581427bc9223a84846dea3) - feat(verification): up expiry time to 30min [`34b28c8`](https://github.com/opengovsg/FormSG/commit/34b28c87c14e8e0b55276e5a21b6f5473c436e24) #### [v5.13.0](https://github.com/opengovsg/FormSG/compare/v5.12.1...v5.13.0) diff --git a/README.md b/README.md index 0c09001278..244d460204 100755 --- a/README.md +++ b/README.md @@ -41,11 +41,11 @@ Notable features include: The current product roadmap includes: -- (in progress) Migrating backend code from JavaScript to [TypeScript](https://www.typescriptlang.org/) -- (in progress) Refactoring backend code to use [Domain-driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) -- (in progress) Migrating backend tests from [Jasmine](https://jasmine.github.io/) to [Jest](https://jestjs.io/) and expanding unit vs integration tests -- (yet to start) Support for webhooks attachments -- (yet to start) Frontend rewrite from [AngularJS](https://angularjs.org/) to [React](https://reactjs.org/) +- (in progress) Frontend rewrite from [AngularJS](https://angularjs.org/) to [React](https://reactjs.org/) +- Enabling payments on forms +- Electronic signatures +- Notifications to form admins for Storage mode submissions +- Integration with vault.gov.sg ## Local Development (Docker) @@ -168,29 +168,6 @@ An overview of the architecture can be found [here](docs/ARCHITECTURE.md). Scripts for common tasks in MongoDB can be found [here](docs/MONGODB.md). -## Maintenance Banners - -Banners providing form-fillers with useful information can shown at the top of -forms and configured using the environment variables below. - -| Environment Variable | Value will be shown as a banner at the bottom of | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ADMIN_BANNER_CONTENT` | private form routes such as `/forms` and `/{formId}/admin` | -| `SITE_BANNER_CONTENT` | both private routes that `ADMIN_BANNER_CONTENT` covers **and** public form routes that `IS_GENERAL_MAINTENANCE` covers. This supersedes **ALL** other banner environment variables | -| `IS_GENERAL_MAINTENANCE` | all public forms | -| `IS_SP_MAINTENANCE` | all public **SingPass-enabled** forms | -| `IS_CP_MAINTENANCE` | all public **CorpPass-enabled** forms | - -> Note that if more than one of the above environment variables are defined, -> only one environment variable will be used to display the given values. -> -> For public form routes, only one environment variable will be read in the -> following precedence: `SITE_BANNER_CONTENT` > `IS_GENERAL_MAINTENANCE` > -> `IS_SP_MAINTENANCE` > `IS_CP_MAINTENANCE` -> -> For private form routes, only one environment variable will be read in the -> following precendence: `SITE_BANNER_CONTENT` > `ADMIN_BANNER_CONTENT` - ## Contributing We welcome all contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas to code open sourced by the Government Technology Agency of Singapore. Contributors should read [CONTRIBUTING.md](CONTRIBUTING.md) and will also be asked to sign a Contributor License Agreement (CLA) in order to ensure that everybody is free to use their contributions. @@ -211,4 +188,4 @@ Contributions have also been made by: [@zioul123](https://github.com/zioul123) [@JoelWee](https://github.com/JoelWee) [@limli](https://github.com/limli) -[@tankevan](https://github.com/tankevan) +[@tankevan](https://github.com/tankevan) diff --git a/docker-compose.yml b/docker-compose.yml index 63c317559d..3aea249828 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,8 +39,12 @@ services: - MYINFO_CLIENT_ID=mockClientId - MYINFO_CLIENT_SECRET=mockClientSecret - WEBHOOK_SQS_URL=http://localhost:4566/000000000000/local-webhooks-sqs-main - - GA_TRACKING_ID - - SENTRY_CONFIG_URL + - INTRANET_IP_LIST_PATH + - SENTRY_CONFIG_URL=https://random@sentry.io/123456 + - CSP_REPORT_URI=https://random@sentry.io/123456 + # This needs to be removed and replaced with a real tracking ID in a local .env file + # in order to enable GA in a local environment + - GA_TRACKING_ID=mockGATrackingId - TWILIO_ACCOUNT_SID - TWILIO_API_KEY - TWILIO_API_SECRET @@ -71,7 +75,6 @@ services: - CORPPASS_IDP_ID - IS_SP_MAINTENANCE - IS_CP_MAINTENANCE - - AGGREGATE_COLLECTION mockpass: build: https://github.com/opengovsg/mockpass.git @@ -112,6 +115,8 @@ services: - './.localstack:/tmp/localstack' - '/var/run/docker.sock:/var/run/docker.sock' network_mode: 'service:formsg' # reuse formsg service's network stack so that it can resolve localhost:4566 to localstack:4566 + logging: + driver: none maildev: image: maildev/maildev diff --git a/docs/DEPLOYMENT_SETUP.md b/docs/DEPLOYMENT_SETUP.md index 4c60788dcf..1d73dead40 100644 --- a/docs/DEPLOYMENT_SETUP.md +++ b/docs/DEPLOYMENT_SETUP.md @@ -173,8 +173,23 @@ SITE_BANNER_CONTENT=hello:This is an invalid banner type, and the full text will | :----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SITE_BANNER_CONTENT` | If set, displays a banner message on both private routes that `ADMIN_BANNER_CONTENT` covers **and** public form routes that `IS_GENERAL_MAINTENANCE` covers. Overrides all other banner environment variables | | `ADMIN_BANNER_CONTENT` | If set, displays a banner message on private admin routes such as the form list page as well as form builder pages. | -| `IS_LOGIN_BANNER` | If set, displays a banner message on the login page | +| `IS_LOGIN_BANNER` | If set, displays a banner message on the login page. | | `IS_GENERAL_MAINTENANCE` | If set, displays a banner message on all forms. Overrides `IS_SP_MAINTENANCE` and `IS_CP_MAINTENANCE`. | +| `IS_SP_MAINTENANCE` | all public **SingPass-enabled** forms | +| `IS_CP_MAINTENANCE` | all public **CorpPass-enabled** forms | + +> Note that if more than one of the above environment variables are defined, +> only one environment variable will be used to display the given values. +> +> For public form routes, only one environment variable will be read in the +> following precedence: `SITE_BANNER_CONTENT` > `IS_GENERAL_MAINTENANCE` > +> `IS_SP_MAINTENANCE` > `IS_CP_MAINTENANCE` +> +> For private form routes, only one environment variable will be read in the +> following precendence: `SITE_BANNER_CONTENT` > `ADMIN_BANNER_CONTENT` +> +> For the login page, only one environment variable will be read in the +> following precendence: `SITE_BANNER_CONTENT` > `IS_LOGIN_BANNER` #### AWS services @@ -222,11 +237,11 @@ The app applies per-minute, per-IP rate limits at specific API endpoints as a se ### Additional Features -The app supports a number of additional features like Captcha protection, Sentry reporting and Google Analytics. Each of these features requires specific environment variables which are detailed below. To deploy a bare bones application without these additional features, one can safely exclude the respective environment variables without any extra configuration. +The app contains a number of additional features like Captcha protection, Sentry reporting and Google Analytics. Each of these features requires specific environment variables which are detailed below. #### Google Captcha -If this feature is enabled, forms with be protected with [recaptcha](https://www.google.com/recaptcha/about/), preventing submissions from being made by bots. +Forms can be protected with [recaptcha](https://www.google.com/recaptcha/about/), preventing submissions from being made by bots. | Variable | Description | | :---------------------- | -------------------------- | @@ -235,7 +250,7 @@ If this feature is enabled, forms with be protected with [recaptcha](https://www #### Google Analytics -If this feature is enabled, [google analytics](https://analytics.google.com/analytics/web) will be used to track website traffic. Examples of events include number of visits to various forms, number of successful submissions and number of submission failures. +[Google Analytics](https://analytics.google.com/analytics/web) is used to track website traffic. Examples of events include number of visits to various forms, number of successful submissions and number of submission failures. | Variable | Description | | :--------------- | ----------------------------- | @@ -243,7 +258,7 @@ If this feature is enabled, [google analytics](https://analytics.google.com/anal #### Sentry.io -If this feature is enabled, client-side error events will be piped to [sentry.io](https://sentry.io/welcome/) for monitoring purposes. +Client-side error events are piped to [sentry.io](https://sentry.io/welcome/) for monitoring purposes. | Variable | Description | | :------------------ | ----------------------------------------------------------------------------------------------------- | @@ -251,21 +266,11 @@ If this feature is enabled, client-side error events will be piped to [sentry.io | `SENTRY_CONFIG_URL` | Sentry.io URL for configuring the Raven SDK. | | `CSP_REPORT_URI` | Reporting URL for Content Security Policy violdations. Can be configured to use a Sentry.io endpoint. | -#### Examples page Using Pre-Computed Results - -If this feature is enabled, the submission statistics associated with forms loaded on the examples page will be fetched from the pre-computed values in the FormStatisticsTotal collection. The FormStatisticsTotal collection only exists if the [batch jobs](https://github.com/datagovsg/formsg-batch-jobs/) needed to calculate the submission statistics are run daily. - -If this feature is not enabled, the submission statistics associated with forms loaded on the examples page will be calculated on the fly from the Submissions collection. This may be sub-optimal when submissions are in the millions. - -| Variable | Description | -| :--------------------- | --------------------------------------------------------------------- | -| `AGGREGATE_COLLECTION` | Has to be defined (i.e. =true) if FormStats collection is to be used. | - #### SMS with Twilio -If this feature is enabled, the Mobile Number field will support form-fillers verifying their mobile numbers via a One-Time-Pin sent to their mobile phones and will also support an SMS confirmation of successful submission being sent out to their said mobile numbers. All messages are sent using [twilio](https://www.twilio.com/) messaging APIs. +The Mobile Number field supports form-fillers verifying their mobile numbers via a One-Time-Pin sent to their mobile phones. All messages are sent using [Twilio](https://www.twilio.com/) messaging APIs. -Note that verifiying mobile numbers also requires [Verified Emails/SMSes](#verified-emailssmses) to be enabled. +Note that verifying mobile numbers also requires the environment variables for [verified Emails/SMSes](#verified-emailssmses). | Variable | Description | | :----------------------------- | ------------------------ | @@ -276,8 +281,8 @@ Note that verifiying mobile numbers also requires [Verified Emails/SMSes](#verif #### SingPass/CorpPass and MyInfo -If this feature is enabled, forms will support authentication via [SingPass](https://www.singpass.gov.sg/singpass/common/aboutus) (Singapore's Digital Identity for Citizens) and -[CorpPass](https://www.corppass.gov.sg/corppass/common/aboutus) (Singapore's Digital Identity for Organizations). Forms will also support pre-filling using [MyInfo](https://www.singpass.gov.sg/myinfo/intro) after a citizen has successfully authenticated using SingPass. +Submissions can be authenticated via [SingPass](https://www.singpass.gov.sg/singpass/common/aboutus) (Singapore's Digital Identity for Citizens) and +[CorpPass](https://www.corppass.gov.sg/corppass/common/aboutus) (Singapore's Digital Identity for Organizations). Forms can also be pre-filled using [MyInfo](https://www.singpass.gov.sg/myinfo/intro) after a citizen has successfully authenticated using SingPass. Note that MyInfo is currently not supported for storage mode forms and enabling SingPass/CorpPass on storage mode forms also requires [SingPass/CorpPass for Storage Mode](#webhooks-and-singpasscorppass-for-storage-mode) to be enabled. @@ -312,9 +317,9 @@ Note that MyInfo is currently not supported for storage mode forms and enabling #### Verified Emails/SMSes -If this feature is enabled, the Mobile Number field will support form-fillers verifying their mobile numbers via a One-Time-Pin sent to their mobile phones and the Email field will support form-fillers verifying their email addresses via a One-Time-Pin sent to their email boxes. +The Mobile Number and Email fields support form-fillers verifying their contact details via a One-Time-Pin. -Note that verified SMSes also requires [SMS with Twilio](#sms-with-twilio) to be enabled. +Note that verified SMSes also require [SMS with Twilio](#sms-with-twilio) to be enabled. | Variable | Description | | :------------------------ | -------------------------------------------------------------- | @@ -322,9 +327,9 @@ Note that verified SMSes also requires [SMS with Twilio](#sms-with-twilio) to be #### Webhooks and SingPass/CorpPass for Storage Mode -If this feature is enabled, storage mode forms will support posting encrypted form submissions to a REST API supplied by the form creator. The [FormSG SDK](https://github.com/opengovsg/formsg-javascript-sdk) can then be used to verify the signed posted data and decrypt the encrypted submission contained within. +Form admins can configure their Storage mode forms to POST encrypted form submissions to a REST API supplied by the form creator. The [FormSG SDK](https://github.com/opengovsg/formsg-javascript-sdk) can then be used to verify the signed posted data and decrypt the encrypted submission contained within. -If this feature is enabled, storage mode forms will also support authentication via SingPass or CorpPass. Note that this also requires [SingPass/CorpPass and MyInfo](#singpasscorppass-and-myinfo) to be enabled. +These environment variables also allow Storage mode forms to support authentication via SingPass or CorpPass. Note that this also requires [SingPass/CorpPass and MyInfo](#singpasscorppass-and-myinfo) to be enabled. | Variable | Description | | :------------------- | --------------------------------------------------------------------- | diff --git a/package-lock.json b/package-lock.json index 4ac6f2061d..6753c5bc0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "FormSG", - "version": "5.13.1", + "version": "5.15.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20,20 +20,20 @@ "dev": true }, "@babel/core": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", - "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.3", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.3", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.5.tgz", + "integrity": "sha512-RN/AwP2DJmQTZSfiDaD+JQQ/J99KsIpOCfBE5pL+5jJSt7nI3nYGoAXZu+ffYSQ029NLs2DstZb+eR81uuARgg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -43,129 +43,209 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", + "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", "dev": true }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", "dev": true, "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", "semver": "^6.3.0" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "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==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -183,9 +263,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "version": "1.0.30001236", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz", + "integrity": "sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ==", "dev": true }, "colorette": { @@ -204,9 +284,9 @@ } }, "electron-to-chromium": { - "version": "1.3.730", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.730.tgz", - "integrity": "sha512-1Tr3h09wXhmqXnvDyrRe6MFgTeU0ZXy3+rMJWTrOHh/HNesWwBBrKnMxRJWZ86dzs8qQdw2c7ZE1/qeGHygImA==", + "version": "1.3.752", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", + "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, "json5": { @@ -219,9 +299,9 @@ } }, "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, "semver": { @@ -499,37 +579,37 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.3.tgz", - "integrity": "sha512-JIB2+XJrb7v3zceV2XzDhGIB902CmKGSpSl4q2C6agU9SNLG/2V1RtFRGPG1Ajh9STj3+q6zJMOC+N/pp2P9DA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", + "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.14.5", "regexpu-core": "^4.7.1" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } } @@ -547,9 +627,9 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", - "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", @@ -563,117 +643,118 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/generator": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz", - "integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.1", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", - "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", - "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.0", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.14.0", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz", - "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -743,132 +824,29 @@ } }, "@babel/helper-hoist-variables": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", - "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", "dev": true, "requires": { - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.16" + "@babel/types": "^7.14.5" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } } } }, @@ -909,185 +887,10 @@ } } }, - "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/generator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.2.tgz", - "integrity": "sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.2.tgz", - "integrity": "sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ==", - "dev": true - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "@babel/helper-optimise-call-expression": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", + "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", "dev": true, "requires": { "@babel/types": "^7.10.1" @@ -1265,56 +1068,28 @@ "@babel/types": "^7.10.1" } }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/types": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", - "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - } - } - }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz", + "integrity": "sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.14.5" }, "dependencies": { "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } } @@ -1354,122 +1129,132 @@ } }, "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.5.tgz", + "integrity": "sha512-xtcWOuN9VL6nApgVHtq3PPcQv5qFBJzoSZzJ/2c0QK/IP/gxVcoWSNQwFEGvmbQsuS9rhYqjILDGGXcTkA705Q==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -1502,20 +1287,20 @@ "dev": true }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", - "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.13.12" + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -1558,181 +1343,182 @@ } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.3.tgz", - "integrity": "sha512-HEjzp5q+lWSjAgJtSluFDrGGosmwTgKwCXdDQZvhKsRlwv3YdkUEqxNrrjesJd+B9E9zvr1PVPVBvhYZ9msjvQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz", + "integrity": "sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.3", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-class-static-block": "^7.12.13" + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.4.tgz", - "integrity": "sha512-idr3pthFlDCpV+p/rMgGLGYIVtazeatrSOQk8YzO2pAepIjQhCN3myeihVg58ax2bbbGK9PUE1reFi7axOYIOw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.5.tgz", + "integrity": "sha512-Uq9z2e7ZtcnDMirRqAGLRaLwJn+Lrh388v5ETrR3pALJnElVh2zqQmdbz4W2RUJYohAPh2mtyPUgyMHMzXMncQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.14.4", - "@babel/helper-split-export-declaration": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", - "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.4" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -1938,37 +1724,37 @@ } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz", - "integrity": "sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", + "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-export-namespace-from": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz", - "integrity": "sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", + "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -1985,73 +1771,73 @@ } }, "@babel/plugin-proposal-json-strings": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz", - "integrity": "sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", + "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-logical-assignment-operators": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz", - "integrity": "sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", + "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz", - "integrity": "sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", + "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-numeric-separator": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz", - "integrity": "sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", + "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2078,237 +1864,410 @@ } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz", - "integrity": "sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", + "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-optional-chaining": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz", - "integrity": "sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", + "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-proposal-private-methods": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", - "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", + "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.5.tgz", + "integrity": "sha512-Uq9z2e7ZtcnDMirRqAGLRaLwJn+Lrh388v5ETrR3pALJnElVh2zqQmdbz4W2RUJYohAPh2mtyPUgyMHMzXMncQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } } } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-create-class-features-plugin": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0" + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.4.tgz", - "integrity": "sha512-idr3pthFlDCpV+p/rMgGLGYIVtazeatrSOQk8YzO2pAepIjQhCN3myeihVg58ax2bbbGK9PUE1reFi7axOYIOw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.5.tgz", + "integrity": "sha512-Uq9z2e7ZtcnDMirRqAGLRaLwJn+Lrh388v5ETrR3pALJnElVh2zqQmdbz4W2RUJYohAPh2mtyPUgyMHMzXMncQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.14.4", - "@babel/helper-split-export-declaration": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", - "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.4" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -2324,19 +2283,19 @@ } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", + "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2377,18 +2336,18 @@ } }, "@babel/plugin-syntax-class-static-block": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz", - "integrity": "sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2568,18 +2527,18 @@ } }, "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz", - "integrity": "sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2630,18 +2589,18 @@ } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", + "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2673,70 +2632,70 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", - "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", + "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-destructuring": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.4.tgz", - "integrity": "sha512-JyywKreTCGTUsL1OKu1A3ms/R1sTP0WxbpXlALeGzF53eB3bxtNkYdMj9SDgK7g6ImPy76J5oYYKoTtQImlhQA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", + "integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", + "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", + "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -2787,392 +2746,1119 @@ } }, "@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", + "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } } } }, "@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", + "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", + "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-modules-amd": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz", - "integrity": "sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz", - "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-simple-access": "^7.13.12", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", - "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", + "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.13.0", - "@babel/helper-module-transforms": "^7.13.0", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", "babel-plugin-dynamic-import-node": "^2.3.3" }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz", - "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.14.0", - "@babel/helper-plugin-utils": "^7.13.0" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" - }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", - "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.4" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz", + "integrity": "sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@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.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz", + "integrity": "sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@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.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", + "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", + "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@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.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", + "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", + "integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.14.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", + "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "dependencies": { + "@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/plugin-transform-object-super": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", + "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@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.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@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==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -3205,18 +3891,18 @@ } }, "@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", + "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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 } } @@ -3346,73 +4032,73 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", - "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", + "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", + "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-runtime": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.3.tgz", - "integrity": "sha512-t960xbi8wpTFE623ef7sd+UpEC5T6EEguQlTBJDEO05+XwnIWVfuqLw/vdLWY6IdFmtZE+65CZAfByT39zRpkg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.5.tgz", + "integrity": "sha512-fPMBhh1AV8ZyneiCIA+wYYUH1arzlXR1UMcApjvchDhfKxhy2r2lReJv8uHEyihi4IFIGlr1Pdx7S5fkESDQsg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "babel-plugin-polyfill-corejs2": "^0.2.2", + "babel-plugin-polyfill-corejs3": "^0.2.2", + "babel-plugin-polyfill-regenerator": "^0.2.2", "semver": "^6.3.0" }, "dependencies": { "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, @@ -3425,155 +4111,155 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", + "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-spread": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", - "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz", + "integrity": "sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", + "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-template-literals": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", - "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", + "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", + "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", + "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", + "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" }, "dependencies": { "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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/preset-env": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.4.tgz", - "integrity": "sha512-GwMMsuAnDtULyOtuxHhzzuSRxFeP0aR/LNzrHRzP8y6AgDNgqnrfCCBm/1cRdTU75tRs28Eh76poHLcg9VF0LA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.14.4", - "@babel/helper-compilation-targets": "^7.14.4", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-validator-option": "^7.12.17", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.14.2", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-class-static-block": "^7.14.3", - "@babel/plugin-proposal-dynamic-import": "^7.14.2", - "@babel/plugin-proposal-export-namespace-from": "^7.14.2", - "@babel/plugin-proposal-json-strings": "^7.14.2", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.2", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", - "@babel/plugin-proposal-numeric-separator": "^7.14.2", - "@babel/plugin-proposal-object-rest-spread": "^7.14.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.2", - "@babel/plugin-proposal-optional-chaining": "^7.14.2", - "@babel/plugin-proposal-private-methods": "^7.13.0", - "@babel/plugin-proposal-private-property-in-object": "^7.14.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.5.tgz", + "integrity": "sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-async-generator-functions": "^7.14.5", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-class-static-block": "^7.14.5", + "@babel/plugin-proposal-dynamic-import": "^7.14.5", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-proposal-json-strings": "^7.14.5", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", + "@babel/plugin-proposal-numeric-separator": "^7.14.5", + "@babel/plugin-proposal-object-rest-spread": "^7.14.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", + "@babel/plugin-proposal-optional-chaining": "^7.14.5", + "@babel/plugin-proposal-private-methods": "^7.14.5", + "@babel/plugin-proposal-private-property-in-object": "^7.14.5", + "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.3", @@ -3583,195 +4269,303 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.0", - "@babel/plugin-syntax-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.13.0", - "@babel/plugin-transform-async-to-generator": "^7.13.0", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.14.4", - "@babel/plugin-transform-classes": "^7.14.4", - "@babel/plugin-transform-computed-properties": "^7.13.0", - "@babel/plugin-transform-destructuring": "^7.14.4", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.13.0", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.14.2", - "@babel/plugin-transform-modules-commonjs": "^7.14.0", - "@babel/plugin-transform-modules-systemjs": "^7.13.8", - "@babel/plugin-transform-modules-umd": "^7.14.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.14.2", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.13.15", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.13.0", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.13.0", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.14.5", + "@babel/plugin-transform-async-to-generator": "^7.14.5", + "@babel/plugin-transform-block-scoped-functions": "^7.14.5", + "@babel/plugin-transform-block-scoping": "^7.14.5", + "@babel/plugin-transform-classes": "^7.14.5", + "@babel/plugin-transform-computed-properties": "^7.14.5", + "@babel/plugin-transform-destructuring": "^7.14.5", + "@babel/plugin-transform-dotall-regex": "^7.14.5", + "@babel/plugin-transform-duplicate-keys": "^7.14.5", + "@babel/plugin-transform-exponentiation-operator": "^7.14.5", + "@babel/plugin-transform-for-of": "^7.14.5", + "@babel/plugin-transform-function-name": "^7.14.5", + "@babel/plugin-transform-literals": "^7.14.5", + "@babel/plugin-transform-member-expression-literals": "^7.14.5", + "@babel/plugin-transform-modules-amd": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5", + "@babel/plugin-transform-modules-systemjs": "^7.14.5", + "@babel/plugin-transform-modules-umd": "^7.14.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.5", + "@babel/plugin-transform-new-target": "^7.14.5", + "@babel/plugin-transform-object-super": "^7.14.5", + "@babel/plugin-transform-parameters": "^7.14.5", + "@babel/plugin-transform-property-literals": "^7.14.5", + "@babel/plugin-transform-regenerator": "^7.14.5", + "@babel/plugin-transform-reserved-words": "^7.14.5", + "@babel/plugin-transform-shorthand-properties": "^7.14.5", + "@babel/plugin-transform-spread": "^7.14.5", + "@babel/plugin-transform-sticky-regex": "^7.14.5", + "@babel/plugin-transform-template-literals": "^7.14.5", + "@babel/plugin-transform-typeof-symbol": "^7.14.5", + "@babel/plugin-transform-unicode-escapes": "^7.14.5", + "@babel/plugin-transform-unicode-regex": "^7.14.5", "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.14.4", - "babel-plugin-polyfill-corejs2": "^0.2.0", - "babel-plugin-polyfill-corejs3": "^0.2.0", - "babel-plugin-polyfill-regenerator": "^0.2.0", - "core-js-compat": "^3.9.0", + "@babel/types": "^7.14.5", + "babel-plugin-polyfill-corejs2": "^0.2.2", + "babel-plugin-polyfill-corejs3": "^0.2.2", + "babel-plugin-polyfill-regenerator": "^0.2.2", + "core-js-compat": "^3.14.0", "semver": "^6.3.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", - "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", + "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", "dev": true }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", + "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.14.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz", + "integrity": "sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-compilation-targets": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", - "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", + "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", "dev": true, "requires": { - "@babel/compat-data": "^7.14.4", - "@babel/helper-validator-option": "^7.12.17", + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", "browserslist": "^4.16.6", "semver": "^6.3.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.5.tgz", + "integrity": "sha512-Uq9z2e7ZtcnDMirRqAGLRaLwJn+Lrh388v5ETrR3pALJnElVh2zqQmdbz4W2RUJYohAPh2mtyPUgyMHMzXMncQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", + "integrity": "sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", + "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", + "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "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-remap-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz", + "integrity": "sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-wrap-function": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, "@babel/helper-replace-supers": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", - "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", + "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.4" + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "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==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz", + "integrity": "sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", + "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", "dev": true }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", + "integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", + "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.4.tgz", - "integrity": "sha512-AYosOWBlyyXEagrPRfLJ1enStufsr7D1+ddpj8OLi9k7B6+NdZ0t/9V7Fh+wJ4g2Jol8z2JkgczYqtWrZd4vbA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", + "integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", "dev": true, "requires": { - "@babel/compat-data": "^7.14.4", - "@babel/helper-compilation-targets": "^7.14.4", - "@babel/helper-plugin-utils": "^7.13.0", + "@babel/compat-data": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.14.2" + "@babel/plugin-transform-parameters": "^7.14.5" } }, "@babel/plugin-syntax-class-properties": { @@ -3784,102 +4578,154 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", - "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", + "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", + "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-remap-async-to-generator": "^7.14.5" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.4.tgz", - "integrity": "sha512-5KdpkGxsZlTk+fPleDtGKsA+pon28+ptYmMO8GBSa5fHERCJWAzj50uAfCKBqq42HO+Zot6JF1x37CRprwmN4g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.5.tgz", + "integrity": "sha512-LBYm4ZocNgoCqyxMLoOnwpsmQ18HWTQvql64t3GvMUzLQrNoV1BDG0lNftC8QKYERkZgCCT/7J5xWGObGAyHDw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.4.tgz", - "integrity": "sha512-p73t31SIj6y94RDVX57rafVjttNr8MvKEgs5YFatNB/xC68zM3pyosuOEcQmYsYlyQaGY9R7rAULVRcat5FKJQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.5.tgz", + "integrity": "sha512-J4VxKAMykM06K/64z9rwiL6xnBHgB1+FVspqvlgCdwD1KUbQNfszeKVVOMh59w3sztHYIZDgnhOC4WbdEfHFDA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/helper-replace-supers": "^7.14.4", - "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", "globals": "^11.1.0" } }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", + "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", + "@babel/helper-plugin-utils": "^7.14.5" + } + }, "@babel/plugin-transform-for-of": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", - "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz", + "integrity": "sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/plugin-transform-parameters": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz", - "integrity": "sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz", + "integrity": "sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.13.0" + "@babel/helper-plugin-utils": "^7.14.5" } }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", + "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "to-fast-properties": "^2.0.0" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.2.tgz", + "integrity": "sha512-l1Cf8PKk12eEk5QP/NQ6TH8A1pee6wWDJ96WjxrMXFLHLOBFzYM4moG80HFgduVhTqAFez4alnZKEhP/bYHg0A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.9.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2" + } + }, "browserslist": { "version": "4.16.6", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", @@ -3894,9 +4740,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001230", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz", - "integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==", + "version": "1.0.30001236", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz", + "integrity": "sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ==", "dev": true }, "colorette": { @@ -3905,6 +4751,24 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", "dev": true }, + "core-js-compat": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", + "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -3915,15 +4779,15 @@ } }, "electron-to-chromium": { - "version": "1.3.742", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.742.tgz", - "integrity": "sha512-ihL14knI9FikJmH2XUIDdZFWJxvr14rPSdOhJ7PpS27xbz8qmaRwCwyg/bmFwjWKmWK9QyamiCZVCvXm5CH//Q==", + "version": "1.3.752", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", + "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, "semver": { @@ -3989,9 +4853,9 @@ } }, "@babel/runtime": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", - "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.5.tgz", + "integrity": "sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -4911,9 +5775,9 @@ "integrity": "sha512-YqR6GIsum9K7Cg6wOTxwJnKP+KDOxbZ9dnQE2/M47vP0ynXyTadvwflGBukzJ/MhzrS2R6buNhFjFnVJRXJinw==" }, "@opengovsg/spcp-auth-client": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@opengovsg/spcp-auth-client/-/spcp-auth-client-1.4.7.tgz", - "integrity": "sha512-Elt4eg0HozU5cjzpycAKDPhp0phGvcgt0iPOuWmL3a1D6KHszKMWtbMPE64Qfz0toAaFgFshJVJh3fVIx8yncw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@opengovsg/spcp-auth-client/-/spcp-auth-client-1.4.8.tgz", + "integrity": "sha512-ewC5Fxwj+y6zr+Nymx9FySpdewood/p4O/11n7uiOkmqJWjHW7rPCMbM5WNQOhotkWlDrlgkzsvXLfsmxI3nPw==", "requires": { "base-64": "^1.0.0", "jsonwebtoken": "^8.3.0", @@ -4927,125 +5791,125 @@ } }, "@sentry/browser": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.5.1.tgz", - "integrity": "sha512-iVLCdEFwsoWAzE/hNknexPQjjDpMQV7mmaq9Z1P63bD6MfhwVTx4hG4pHn8HEvC38VvCVf1wv0v/LxtoODAYXg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.0.tgz", + "integrity": "sha512-sZvy2fxHjHXPdlaz8Ax02BeUbdILRv6a4i9FvMHvgSBeDiAVRIS+ihBhJAqziNOqwwXYThCSPKcCYGyTTncrVw==", "requires": { - "@sentry/core": "6.5.1", - "@sentry/types": "6.5.1", - "@sentry/utils": "6.5.1", + "@sentry/core": "6.7.0", + "@sentry/types": "6.7.0", + "@sentry/utils": "6.7.0", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", - "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.0.tgz", + "integrity": "sha512-5pKv0yJEOnkjy3J3eiGaM1CD2+p3rXkctJa8loZH7QgY7mJgUTKpozO3YymUmGjblthlrbuhH+5wUIBnVF60Bg==" }, "@sentry/utils": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", - "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-K6s9svqOF4TT4AwvlDdiV9ZSGStSnf64s8KH1DNqwu5EZULvXvg0kbqgi6ZJTDHcchbnwHm7hLMNfuw95Aqi4Q==", "requires": { - "@sentry/types": "6.5.1", + "@sentry/types": "6.7.0", "tslib": "^1.9.3" } } } }, "@sentry/core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.5.1.tgz", - "integrity": "sha512-Mh3sl/iUOT1myHmM6RlDy2ARzkUClx/g4DAt1rJ/IpQBOlDYQraplXSIW80i/hzRgQDfwhwgf4wUa5DicKBjKw==", - "requires": { - "@sentry/hub": "6.5.1", - "@sentry/minimal": "6.5.1", - "@sentry/types": "6.5.1", - "@sentry/utils": "6.5.1", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.0.tgz", + "integrity": "sha512-1TzDQIsS71a+6T1o3+NPyIgsTc37wdGh7cKZ8DRQ4bsML7MAkBV/LJeTVbXa0S9xha1v9v/oPindnHX5vBLJbg==", + "requires": { + "@sentry/hub": "6.7.0", + "@sentry/minimal": "6.7.0", + "@sentry/types": "6.7.0", + "@sentry/utils": "6.7.0", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", - "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.0.tgz", + "integrity": "sha512-5pKv0yJEOnkjy3J3eiGaM1CD2+p3rXkctJa8loZH7QgY7mJgUTKpozO3YymUmGjblthlrbuhH+5wUIBnVF60Bg==" }, "@sentry/utils": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", - "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-K6s9svqOF4TT4AwvlDdiV9ZSGStSnf64s8KH1DNqwu5EZULvXvg0kbqgi6ZJTDHcchbnwHm7hLMNfuw95Aqi4Q==", "requires": { - "@sentry/types": "6.5.1", + "@sentry/types": "6.7.0", "tslib": "^1.9.3" } } } }, "@sentry/hub": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.5.1.tgz", - "integrity": "sha512-lBRMBVMYP8B4PfRiM70murbtJAXiIAao/asDEMIRNGMP6pI2ArqXfJCBYDkStukhikYD0Kqb4trXq+JYF07Hbg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.0.tgz", + "integrity": "sha512-8e1IF6v8OIjuZVsolBAFoHhY0fEolsWwmZzm9k5N1wXWRbu4gpLHnCtDw47u2O9CFYr+b//bNXjmsA+DTckPkw==", "requires": { - "@sentry/types": "6.5.1", - "@sentry/utils": "6.5.1", + "@sentry/types": "6.7.0", + "@sentry/utils": "6.7.0", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", - "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.0.tgz", + "integrity": "sha512-5pKv0yJEOnkjy3J3eiGaM1CD2+p3rXkctJa8loZH7QgY7mJgUTKpozO3YymUmGjblthlrbuhH+5wUIBnVF60Bg==" }, "@sentry/utils": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", - "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-K6s9svqOF4TT4AwvlDdiV9ZSGStSnf64s8KH1DNqwu5EZULvXvg0kbqgi6ZJTDHcchbnwHm7hLMNfuw95Aqi4Q==", "requires": { - "@sentry/types": "6.5.1", + "@sentry/types": "6.7.0", "tslib": "^1.9.3" } } } }, "@sentry/integrations": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.5.1.tgz", - "integrity": "sha512-NYiW0rH7fwv7aRtrRnfCSIiwulfV2NoLjhmghCONsyo10DNtYmOpogLotCytZFWLDnTJW1+pmTomq8UW/OSTcQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.7.0.tgz", + "integrity": "sha512-sEmlbE84Ljcbu5fqmOHv5IgbYKhKBsiGzOhgLdMMchIMPAnJ2vc22jq0xaqLUO6WCmRgJZjeManYkXtImFu44g==", "requires": { - "@sentry/types": "6.5.1", - "@sentry/utils": "6.5.1", + "@sentry/types": "6.7.0", + "@sentry/utils": "6.7.0", "localforage": "^1.8.1", "tslib": "^1.9.3" } }, "@sentry/minimal": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.5.1.tgz", - "integrity": "sha512-q9Do/oreu1RP695CXCLowVDuQyk7ilE6FGdz2QLpTXAfx8247qOwk6+zy9Kea/Djk93+BoSDVQUSneNiVwl0nQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.0.tgz", + "integrity": "sha512-q0SX2t1+6c8TSe8nI4+EsWc8+kSsKiGhoGo2tN2OTk4EXKCYEsEEDqB9iu7md5StmtmrO3UnRiYwT7JV8QGOeg==", "requires": { - "@sentry/hub": "6.5.1", - "@sentry/types": "6.5.1", + "@sentry/hub": "6.7.0", + "@sentry/types": "6.7.0", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", - "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.0.tgz", + "integrity": "sha512-5pKv0yJEOnkjy3J3eiGaM1CD2+p3rXkctJa8loZH7QgY7mJgUTKpozO3YymUmGjblthlrbuhH+5wUIBnVF60Bg==" } } }, "@sentry/types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", - "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.0.tgz", + "integrity": "sha512-5pKv0yJEOnkjy3J3eiGaM1CD2+p3rXkctJa8loZH7QgY7mJgUTKpozO3YymUmGjblthlrbuhH+5wUIBnVF60Bg==" }, "@sentry/utils": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", - "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.0.tgz", + "integrity": "sha512-K6s9svqOF4TT4AwvlDdiV9ZSGStSnf64s8KH1DNqwu5EZULvXvg0kbqgi6ZJTDHcchbnwHm7hLMNfuw95Aqi4Q==", "requires": { - "@sentry/types": "6.5.1", + "@sentry/types": "6.7.0", "tslib": "^1.9.3" } }, @@ -5338,9 +6202,9 @@ } }, "@types/express-rate-limit": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.1.tgz", - "integrity": "sha512-6oMYZBLlhxC5sdcRXXz528QyfGz3zTy9YdHwqlxLfgx5Cd3zwYaUjjPpJcaTtHmRefLi9P8kLBPz2wB7yz4JtQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.2.tgz", + "integrity": "sha512-loN1dcr0WEKsbVtXNQKDef4Fmh25prfy+gESrwTDofIhAt17ngTxrsDiEZxLemmfHH279x206CdUB9XHXS9E6Q==", "dev": true, "requires": { "@types/express": "*" @@ -5519,9 +6383,9 @@ "dev": true }, "@types/mongodb": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.17.tgz", - "integrity": "sha512-9hhgvYPdC5iHyyksPcKCu45gfaAIPQHKHGdvNXu4582DmOZX3wrUJIJPT40o4G1oTKPgpMMFqZglOTjhnYoF+A==", + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.18.tgz", + "integrity": "sha512-JSVFt9p0rTfZ4EgzXmVHUB3ue00xe3CRbQho8nXfImzEDDM4O7I3po1bwbWl/EIbLENxUreZxqLOc8lvcnLVPA==", "requires": { "@types/bson": "*", "@types/node": "*" @@ -5534,9 +6398,9 @@ "dev": true }, "@types/node": { - "version": "14.17.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.2.tgz", - "integrity": "sha512-sld7b/xmFum66AAKuz/rp/CUO8+98fMpyQ3SBfzzBNGMd/1iHBTAg9oyAvcYlAj46bpc74r91jSw2iFdnx29nw==" + "version": "14.17.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", + "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==" }, "@types/nodemailer": { "version": "6.4.2", @@ -5718,9 +6582,9 @@ "dev": true }, "@types/validator": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.3.tgz", - "integrity": "sha512-DaOWN1zf7j+8nHhqXhIgNmS+ltAC53NXqGxYuBhWqWgqolRhddKzfZU814lkHQSTG0IUfQxU7Cg0gb8fFWo2mA==", + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.4.tgz", + "integrity": "sha512-19C02B8mr53HufY7S+HO/EHBD7a/R22IwEwyqiHaR19iwL37dN3o0M8RianVInfSSqP7InVSg/o0mUATM4JWsQ==", "dev": true }, "@types/yargs": { @@ -5748,13 +6612,13 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz", - "integrity": "sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", + "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.26.1", - "@typescript-eslint/scope-manager": "4.26.1", + "@typescript-eslint/experimental-utils": "4.27.0", + "@typescript-eslint/scope-manager": "4.27.0", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.21", @@ -5770,43 +6634,43 @@ "dev": true }, "@typescript-eslint/experimental-utils": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz", - "integrity": "sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", + "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.26.1", - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/typescript-estree": "4.26.1", + "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/typescript-estree": "4.27.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/scope-manager": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", - "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", + "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1" + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0" } }, "@typescript-eslint/types": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", - "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", + "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", - "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", + "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -5815,12 +6679,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", - "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", + "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/types": "4.27.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -5939,41 +6803,41 @@ } }, "@typescript-eslint/parser": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.26.1.tgz", - "integrity": "sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", + "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.26.1", - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/typescript-estree": "4.26.1", + "@typescript-eslint/scope-manager": "4.27.0", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/typescript-estree": "4.27.0", "debug": "^4.3.1" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", - "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", + "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1" + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0" } }, "@typescript-eslint/types": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", - "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", + "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", - "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", + "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", - "@typescript-eslint/visitor-keys": "4.26.1", + "@typescript-eslint/types": "4.27.0", + "@typescript-eslint/visitor-keys": "4.27.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -5982,12 +6846,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", - "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", + "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/types": "4.27.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -6497,14 +7361,6 @@ "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.8.2.tgz", "integrity": "sha512-M1qNh/30cLJi4yJJ+3YB8saPonRcavz5Dquqz0T/aUySKJhIkUoeCkmF+BcLH4SJ5PBp04yy4CZUUeNRVi7jZA==" }, - "angular-moment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz", - "integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==", - "requires": { - "moment": ">=2.8.0 <3.0.0" - } - }, "angular-permission": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/angular-permission/-/angular-permission-1.1.1.tgz", @@ -7098,9 +7954,9 @@ "integrity": "sha512-24q5Rh3bno7ldoyCq99d6hpnLI+PAMocdeVaaGt/5BTQMprvDwQToHfNnruqN11odCHZZIQbRBw+nZo1lTCH9g==" }, "aws-sdk": { - "version": "2.922.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.922.0.tgz", - "integrity": "sha512-SufbR5TTCK94Zk/xIv4v/m0MM9z+KW999XnjXOyNWGFGHP9/FArjtHtq69+a3KpohYBR1dBj8wUhVjbClmQIBA==", + "version": "2.927.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.927.0.tgz", + "integrity": "sha512-S1dbpq26bQNYBQPHAsZBt0/L/e0FAWFdjjQoU3zdaVIXa08eA9d/oRI3kEs568ErJgBjwWU1CUUlr1byBxKjUQ==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -7446,20 +8302,20 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", - "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", "dev": true, "requires": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.0", + "@babel/helper-define-polyfill-provider": "^0.2.2", "semver": "^6.1.1" }, "dependencies": { "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", + "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==", "dev": true }, "semver": { @@ -7471,22 +8327,22 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", - "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.2.tgz", + "integrity": "sha512-l1Cf8PKk12eEk5QP/NQ6TH8A1pee6wWDJ96WjxrMXFLHLOBFzYM4moG80HFgduVhTqAFez4alnZKEhP/bYHg0A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0", + "@babel/helper-define-polyfill-provider": "^0.2.2", "core-js-compat": "^3.9.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", - "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.0" + "@babel/helper-define-polyfill-provider": "^0.2.2" } }, "babel-plugin-syntax-trailing-function-commas": { @@ -9456,9 +10312,9 @@ "dev": true }, "core-js-compat": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.2.tgz", - "integrity": "sha512-gYhNwu7AJjecNtRrIfyoBabQ3ZG+llfPmg9BifIX8yxIpDyfNLRM73zIjINSm6z3dMdI1nwNC9C7uiy4pIC6cw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", + "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", "dev": true, "requires": { "browserslist": "^4.16.6", @@ -9479,9 +10335,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001221", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001221.tgz", - "integrity": "sha512-b9TOZfND3uGSLjMOrLh8XxSQ41x8mX+9MLJYDM4AAHLfaZHttrLNPrScWjVnBITRZbY5sPpCt7X85n7VSLZ+/g==", + "version": "1.0.30001236", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001236.tgz", + "integrity": "sha512-o0PRQSrSCGJKCPZcgMzl5fUaj5xHe8qA2m4QRvnyY4e1lITqoNkr7q/Oh1NcpGSy0Th97UZ35yoKcINPoq7YOQ==", "dev": true }, "colorette": { @@ -9491,15 +10347,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.726", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.726.tgz", - "integrity": "sha512-dw7WmrSu/JwtACiBzth8cuKf62NKL1xVJuNvyOg0jvruN/n4NLtGYoTzciQquCPNaS2eR+BT5GrxHbslfc/w1w==", + "version": "1.3.752", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", + "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==", "dev": true }, "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", + "version": "1.1.73", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", + "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, "semver": { @@ -9978,9 +10834,9 @@ } }, "csv-parse": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.15.4.tgz", - "integrity": "sha512-OdBbFc0yZhOm17lSxqkirrHlFFVpKRT0wp4DAGoJelsP3LbGzV9LNr7XmM/lrr0uGkCtaqac9UhP8PDHXOAbMg==" + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.0.tgz", + "integrity": "sha512-Zb4tGPANH4SW0LgC9+s9Mnequs9aqn7N3/pCqNbVjs2XhEF6yWNU2Vm4OGl1v2Go9nw8rXt87Cm2QN/o6Vpqgg==" }, "csv-string": { "version": "4.0.1", @@ -10035,8 +10891,7 @@ "date-fns": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.22.1.tgz", - "integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==", - "dev": true + "integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==" }, "dateformat": { "version": "1.0.12", @@ -13765,9 +14620,9 @@ "dev": true }, "htmlhint": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/htmlhint/-/htmlhint-0.14.2.tgz", - "integrity": "sha512-lUCgGVZ/oyCkpgDkIa5IfClwX8Ppy11Dk7XdeVboAGSmKjIuOKx6yy86WS0W08KFtCRuxftzNy+KdQjM4UjqCA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/htmlhint/-/htmlhint-0.15.1.tgz", + "integrity": "sha512-uNbgqqBiD2ko4QQOYAHTPwDMluc9X81NwzlrJN7yExCoM6dHNgRFjVI+4TEdRrF/EqUzl1dpMUn7GYbu/qCKmA==", "dev": true, "requires": { "async": "3.2.0", @@ -15689,6 +16544,12 @@ "pretty-format": "^26.6.2" } }, + "jest-localstorage-mock": { + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.14.tgz", + "integrity": "sha512-B+Y0y3J4wBOHdmcFSicWmVYMFAZFbJvjs1EfRIzUJRg2UAK+YVVUgTn7/MdjENey5xbBKmraBmKY5EX+x1NJXA==", + "dev": true + }, "jest-matcher-utils": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", @@ -24961,9 +25822,9 @@ } }, "ts-essentials": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.1.tgz", - "integrity": "sha512-8lwh3QJtIc1UWhkQtr9XuksXu3O0YQdEE5g79guDfhCaU1FWTDIEDZ1ZSx4HTHUmlJZ8L812j3BZQ4a0aOUkSA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.2.tgz", + "integrity": "sha512-qWPVC1xZGdefbsgFP7tPo+bsgSA2ZIXL1XeEe5M2WoMZxIOr/HbsHxP/Iv75IFhiMHMDGL7cOOwi5SXcgx9mHw==", "dev": true }, "ts-jest": { @@ -27124,9 +27985,9 @@ "dev": true }, "zod": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.0.0.tgz", - "integrity": "sha512-4DBG6siN02ooPB1yvEEqoe32maHzKEdGgtQ2HEz6FnFtgTjwZtzJ3ScuiDgtssWfDyLnQ3MvtSj6ff5ANL4STw==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.2.0.tgz", + "integrity": "sha512-yvcO3FZ8URR+LliMGqaW7tlVOOTzmup3vzKEe9Ds7twyJtdhvYa7dIYr0FbD1wVfWC1OuS83vZfHtCKslPuRhA==" }, "zwitch": { "version": "1.0.5", diff --git a/package.json b/package.json index 7a44e7c5c2..7b299b4c99 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "FormSG", "description": "Form Manager for Government", - "version": "5.13.1", + "version": "5.15.0", "homepage": "https://form.gov.sg", "authors": [ "FormSG " @@ -16,8 +16,8 @@ "npm": "~6.4.0" }, "scripts": { - "test-backend": "env-cmd -f tests/.test-full-env jest --coverage --maxWorkers=4", - "test-backend:watch": "env-cmd -f tests/.test-full-env jest --watch", + "test-backend": "env-cmd -f tests/.test-env jest --coverage --maxWorkers=4", + "test-backend:watch": "env-cmd -f tests/.test-env jest --watch", "test-frontend": "jest --config=tests/unit/frontend/jest.config.js", "build": "npm run build-backend && npm run build-frontend", "build-backend": "tsc -p tsconfig.build.json", @@ -28,15 +28,12 @@ "dev": "docker-compose up --build", "docker-dev": "npm run build-frontend-dev:watch & ts-node-dev --respawn --transpile-only --inspect=0.0.0.0 --exit-child -- src/app/server.ts", "test": "npm run test-backend && npm run test-frontend", - "test-e2e-build": "npm run build-backend && npm run build-frontend-dev", - "test-run": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/app/server.js\" \"node ./tests/mock-webhook-server.js\"", - "testcafe-full-env": "testcafe --skip-js-errors -c 3 chrome:headless ./tests/end-to-end --test-meta full-env=true --app \"npm run test-run\" --app-init-delay 10000", - "testcafe-basic-env": "testcafe --skip-js-errors -c 3 chrome:headless ./tests/end-to-end --test-meta basic-env=true --app \"npm run test-run\" --app-init-delay 10000", "download-binary": "node tests/end-to-end/helpers/get-mongo-binary.js", - "test-e2e-full": "env-cmd -f tests/.test-full-env --use-shell \"npm run download-binary && npm run testcafe-full-env\"", - "test-e2e-basic": "env-cmd -f tests/.test-basic-env --use-shell \"npm run download-binary && npm run testcafe-basic-env\"", - "test-e2e": "npm run test-e2e-build && npm run test-e2e-full && npm run test-e2e-basic", - "test-e2e-ci": "npm run test-e2e-full && npm run test-e2e-basic", + "test-e2e": "npm run test-e2e-build && npm run test-e2e-ci", + "test-e2e-build": "npm run build-backend && npm run build-frontend-dev", + "test-e2e-ci": "env-cmd -f tests/.test-env --use-shell \"npm run download-binary && npm run testcafe-command\"", + "testcafe-command": "testcafe --skip-js-errors -c 3 chrome:headless ./tests/end-to-end --app \"npm run test-e2e-server\" --app-init-delay 10000", + "test-e2e-server": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/app/server.js\" \"node ./tests/mock-webhook-server.js\"", "lint-code": "eslint src/ --quiet --fix", "lint-style": "stylelint '*/**/*.css' --quiet --fix", "lint-html": "htmlhint && prettier --write './src/public/**/*.html' --ignore-path './dist/**' --loglevel silent", @@ -55,7 +52,7 @@ ] }, "dependencies": { - "@babel/runtime": "^7.14.0", + "@babel/runtime": "^7.14.5", "@joi/date": "^2.1.0", "@opengovsg/angular-daterangepicker-webpack": "^1.1.5", "@opengovsg/angular-legacy-sortablejs-maintained": "^1.0.0", @@ -63,9 +60,9 @@ "@opengovsg/formsg-sdk": "^0.8.4-beta.0", "@opengovsg/myinfo-gov-client": "^4.0.0", "@opengovsg/ng-file-upload": "^12.2.15", - "@opengovsg/spcp-auth-client": "^1.4.7", - "@sentry/browser": "^6.5.1", - "@sentry/integrations": "^6.5.1", + "@opengovsg/spcp-auth-client": "^1.4.8", + "@sentry/browser": "^6.7.0", + "@sentry/integrations": "^6.7.0", "@stablelib/base64": "^1.0.1", "JSONStream": "^1.3.5", "abortcontroller-polyfill": "^1.7.3", @@ -75,7 +72,6 @@ "angular-cookies": "~1.8.2", "angular-drag-scroll": "^0.2.1", "angular-messages": "^1.8.2", - "angular-moment": "~1.3.0", "angular-permission": "~1.1.1", "angular-resource": "^1.8.2", "angular-sanitize": "^1.8.2", @@ -84,7 +80,7 @@ "angular-ui-bootstrap": "~2.5.6", "angular-ui-router": "~1.0.29", "aws-info": "^1.2.0", - "aws-sdk": "^2.922.0", + "aws-sdk": "^2.927.0", "axios": "^0.21.1", "bcrypt": "^5.0.1", "bluebird": "^3.5.2", @@ -101,6 +97,7 @@ "cookie-parser": "~1.4.0", "css-toggle-switch": "^4.1.0", "csv-string": "^4.0.1", + "date-fns": "^2.22.1", "dedent-js": "~1.0.1", "ejs": "^3.1.6", "express": "^4.16.4", @@ -157,12 +154,12 @@ "whatwg-fetch": "^3.6.2", "winston": "^3.3.3", "winston-cloudwatch": "^2.5.2", - "zod": "^3.0.0" + "zod": "^3.2.0" }, "devDependencies": { - "@babel/core": "^7.14.3", - "@babel/plugin-transform-runtime": "^7.14.3", - "@babel/preset-env": "^7.14.4", + "@babel/core": "^7.14.5", + "@babel/plugin-transform-runtime": "^7.14.5", + "@babel/preset-env": "^7.14.5", "@opengovsg/mockpass": "^2.7.3", "@types/bcrypt": "^5.0.0", "@types/bluebird": "^3.5.35", @@ -173,7 +170,7 @@ "@types/dedent": "^0.7.0", "@types/ejs": "^3.0.6", "@types/express": "^4.17.12", - "@types/express-rate-limit": "^5.1.1", + "@types/express-rate-limit": "^5.1.2", "@types/express-request-id": "^1.4.1", "@types/express-session": "^1.17.0", "@types/has-ansi": "^3.0.0", @@ -181,9 +178,9 @@ "@types/ip": "^1.1.0", "@types/jest": "^26.0.23", "@types/json-stringify-safe": "^5.0.0", - "@types/mongodb": "^3.6.17", + "@types/mongodb": "^3.6.18", "@types/mongodb-uri": "^0.9.0", - "@types/node": "^14.17.2", + "@types/node": "^14.17.3", "@types/nodemailer": "^6.4.2", "@types/opossum": "^4.1.1", "@types/promise-retry": "^1.1.3", @@ -193,9 +190,9 @@ "@types/triple-beam": "^1.3.2", "@types/uid-generator": "^2.0.2", "@types/uuid": "^8.3.0", - "@types/validator": "^13.1.3", - "@typescript-eslint/eslint-plugin": "^4.26.1", - "@typescript-eslint/parser": "^4.26.1", + "@types/validator": "^13.1.4", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", "auto-changelog": "^2.3.0", "axios-mock-adapter": "^1.19.0", "babel-loader": "^8.2.2", @@ -204,8 +201,7 @@ "core-js": "^3.14.0", "coveralls": "^3.1.0", "css-loader": "^2.1.1", - "csv-parse": "^4.15.4", - "date-fns": "^2.22.1", + "csv-parse": "^4.16.0", "env-cmd": "^10.1.0", "eslint": "^7.28.0", "eslint-config-prettier": "^8.3.0", @@ -218,10 +214,11 @@ "form-data": "^4.0.0", "google-fonts-plugin": "4.1.0", "html-loader": "~0.5.5", - "htmlhint": "^0.14.2", + "htmlhint": "^0.15.1", "husky": "^6.0.0", "jest": "^26.6.3", "jest-extended": "^0.11.5", + "jest-localstorage-mock": "^2.4.14", "jest-mock-axios": "^4.4.0", "lint-staged": "^11.0.0", "maildev": "^1.1.0", @@ -243,7 +240,7 @@ "supertest-session": "^4.1.0", "terser-webpack-plugin": "^1.2.3", "testcafe": "^1.14.2", - "ts-essentials": "^7.0.1", + "ts-essentials": "^7.0.2", "ts-jest": "^26.5.6", "ts-loader": "^7.0.5", "ts-node": "^10.0.0", diff --git a/src/app/config/feature-manager/aggregate-stats.config.ts b/src/app/config/feature-manager/aggregate-stats.config.ts deleted file mode 100644 index c9793a833d..0000000000 --- a/src/app/config/feature-manager/aggregate-stats.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { FeatureNames, RegisterableFeature } from './types' - -const aggregateCollectionFeature: RegisterableFeature = - { - name: FeatureNames.AggregateStats, - schema: { - aggregateCollection: { - doc: 'Has to be defined (i.e. =true) if FormStats collection is to be used', - format: '*', - default: null, - env: 'AGGREGATE_COLLECTION', - }, - }, - } - -export default aggregateCollectionFeature diff --git a/src/app/config/feature-manager/google-analytics.config.ts b/src/app/config/feature-manager/google-analytics.config.ts index 35ef2e042c..2663b8d273 100644 --- a/src/app/config/feature-manager/google-analytics.config.ts +++ b/src/app/config/feature-manager/google-analytics.config.ts @@ -1,16 +1,18 @@ -import { FeatureNames, RegisterableFeature } from './types' +import convict, { Schema } from 'convict' -const googleAnalyticsFeature: RegisterableFeature = - { - name: FeatureNames.GoogleAnalytics, - schema: { - GATrackingID: { - doc: 'Google Analytics tracking ID', - format: String, - default: null, - env: 'GA_TRACKING_ID', - }, - }, - } +export interface IGoogleAnalytics { + GATrackingID: string | null +} -export default googleAnalyticsFeature +const googleAnalyticsSchema: Schema = { + GATrackingID: { + doc: 'Google Analytics tracking ID', + format: String, + default: null, + env: 'GA_TRACKING_ID', + }, +} + +export const googleAnalyticsConfig = convict(googleAnalyticsSchema) + .validate({ allowed: 'strict' }) + .getProperties() diff --git a/src/app/config/feature-manager/index.ts b/src/app/config/feature-manager/index.ts index a3fec885b4..2cecb7b069 100644 --- a/src/app/config/feature-manager/index.ts +++ b/src/app/config/feature-manager/index.ts @@ -1,9 +1,5 @@ import FeatureManager from './util/FeatureManager.class' -import aggregateStats from './aggregate-stats.config' import captcha from './captcha.config' -import googleAnalytics from './google-analytics.config' -import { intranetFeature } from './intranet.config' -import sentry from './sentry.config' import sms from './sms.config' import spcpMyInfo from './spcp-myinfo.config' import verifiedFields from './verified-fields.config' @@ -15,13 +11,9 @@ const featureManager = new FeatureManager() // Register features and associated middleware/fallbacks featureManager.register(captcha) -featureManager.register(sentry) -featureManager.register(googleAnalytics) -featureManager.register(aggregateStats) featureManager.register(spcpMyInfo) featureManager.register(webhookVerifiedContent) featureManager.register(sms) featureManager.register(verifiedFields) -featureManager.register(intranetFeature) export default featureManager diff --git a/src/app/config/feature-manager/intranet.config.ts b/src/app/config/feature-manager/intranet.config.ts index 65a924df64..a82294c1ee 100644 --- a/src/app/config/feature-manager/intranet.config.ts +++ b/src/app/config/feature-manager/intranet.config.ts @@ -1,13 +1,18 @@ -import { FeatureNames, RegisterableFeature } from './types' +import convict, { Schema } from 'convict' -export const intranetFeature: RegisterableFeature = { - name: FeatureNames.Intranet, - schema: { - intranetIpListPath: { - doc: 'Path to file containing list of intranet IP addresses, separated by newlines', - format: String, - default: null, - env: 'INTRANET_IP_LIST_PATH', - }, +export interface IIntranet { + intranetIpListPath: string +} + +const intranetSchema: Schema = { + intranetIpListPath: { + doc: 'Path to file containing list of intranet IP addresses, separated by newlines', + format: String, + default: '', + env: 'INTRANET_IP_LIST_PATH', }, } + +export const intranetConfig = convict(intranetSchema) + .validate({ allowed: 'strict' }) + .getProperties() diff --git a/src/app/config/feature-manager/sentry.config.ts b/src/app/config/feature-manager/sentry.config.ts index a9bfc6e30d..efa4f4cd75 100644 --- a/src/app/config/feature-manager/sentry.config.ts +++ b/src/app/config/feature-manager/sentry.config.ts @@ -1,21 +1,28 @@ -import { FeatureNames, RegisterableFeature } from './types' +import convict, { Schema } from 'convict' +import { url } from 'convict-format-with-validator' -const sentryFeature: RegisterableFeature = { - name: FeatureNames.Sentry, - schema: { - sentryConfigUrl: { - doc: 'Sentry.io URL for configuring the Sentry SDK', - format: 'url', - default: null, - env: 'SENTRY_CONFIG_URL', - }, - cspReportUri: { - doc: 'Endpoint for content security policy reporting', - format: 'url', - default: null, - env: 'CSP_REPORT_URI', - }, +export interface ISentry { + sentryConfigUrl: string + cspReportUri: string +} + +convict.addFormat(url) + +const sentryFeature: Schema = { + sentryConfigUrl: { + doc: 'Sentry.io URL for configuring the Sentry SDK', + format: 'url', + default: null, + env: 'SENTRY_CONFIG_URL', + }, + cspReportUri: { + doc: 'Endpoint for content security policy reporting', + format: 'url', + default: null, + env: 'CSP_REPORT_URI', }, } -export default sentryFeature +export const sentryConfig = convict(sentryFeature) + .validate({ allowed: 'strict' }) + .getProperties() diff --git a/src/app/config/feature-manager/types.ts b/src/app/config/feature-manager/types.ts index 6e2b3ac337..e73962b516 100644 --- a/src/app/config/feature-manager/types.ts +++ b/src/app/config/feature-manager/types.ts @@ -2,19 +2,11 @@ import { MyInfoMode } from '@opengovsg/myinfo-gov-client' import { Schema } from 'convict' export enum FeatureNames { - AggregateStats = 'aggregate-stats', Captcha = 'captcha', - GoogleAnalytics = 'google-analytics', - Sentry = 'sentry', Sms = 'sms', SpcpMyInfo = 'spcp-myinfo', VerifiedFields = 'verified-fields', WebhookVerifiedContent = 'webhook-verified-content', - Intranet = 'intranet', -} - -export interface IAggregateStats { - aggregateCollection: string } export interface ICaptcha { @@ -22,15 +14,6 @@ export interface ICaptcha { captchaPublicKey: string } -export interface IGoogleAnalytics { - GATrackingID: string -} - -export interface ISentry { - sentryConfigUrl: string - cspReportUri: string -} - export interface ISms { twilioAccountSid: string twilioApiKey: string @@ -82,20 +65,12 @@ export interface IWebhookVerifiedContent { webhookQueueUrl: string } -export interface IIntranet { - intranetIpListPath: string -} - export interface IFeatureManager { - [FeatureNames.AggregateStats]: IAggregateStats [FeatureNames.Captcha]: ICaptcha - [FeatureNames.GoogleAnalytics]: IGoogleAnalytics - [FeatureNames.Sentry]: ISentry [FeatureNames.Sms]: ISms [FeatureNames.SpcpMyInfo]: ISpcpMyInfo [FeatureNames.VerifiedFields]: IVerifiedFields [FeatureNames.WebhookVerifiedContent]: IWebhookVerifiedContent - [FeatureNames.Intranet]: IIntranet } export interface RegisteredFeature { diff --git a/src/app/loaders/express/__tests__/helmet.spec.ts b/src/app/loaders/express/__tests__/helmet.spec.ts index e41cf98247..c3b91117a9 100644 --- a/src/app/loaders/express/__tests__/helmet.spec.ts +++ b/src/app/loaders/express/__tests__/helmet.spec.ts @@ -2,7 +2,7 @@ import helmet from 'helmet' import { mocked } from 'ts-jest/utils' import config from 'src/app/config/config' -import featureManager from 'src/app/config/feature-manager' +import { sentryConfig } from 'src/app/config/feature-manager/sentry.config' import expressHandler from 'tests/unit/backend/helpers/jest-express' @@ -11,10 +11,10 @@ import helmetMiddlewares from '../helmet' describe('helmetMiddlewares', () => { jest.mock('helmet') const mockHelmet = mocked(helmet, true) - jest.mock('src/app/config/feature-manager') - const mockFeatureManager = mocked(featureManager, true) jest.mock('src/app/config/config') const mockConfig = mocked(config, true) + jest.mock('src/app/config/feature-manager/sentry.config') + const mockSentryConfig = mocked(sentryConfig, true) const cspCoreDirectives = { defaultSrc: ["'self'"], @@ -136,9 +136,7 @@ describe('helmetMiddlewares', () => { }) it('should call helmet.contentSecurityPolicy() with the correct directives if cspReportUri and !isDev', () => { - mockFeatureManager.props = jest - .fn() - .mockReturnValue({ cspReportUri: 'value' }) + mockSentryConfig.cspReportUri = 'value' mockConfig.isDev = false helmetMiddlewares() expect(mockHelmet.contentSecurityPolicy).toHaveBeenCalledWith({ @@ -151,7 +149,7 @@ describe('helmetMiddlewares', () => { }) it('should call helmet.contentSecurityPolicy() with the correct directives if !cspReportUri and isDev', () => { - mockFeatureManager.props = jest.fn() + mockSentryConfig.cspReportUri = '' mockConfig.isDev = true helmetMiddlewares() expect(mockHelmet.contentSecurityPolicy).toHaveBeenCalledWith({ diff --git a/src/app/loaders/express/helmet.ts b/src/app/loaders/express/helmet.ts index 05e6da84a6..7baef9a361 100644 --- a/src/app/loaders/express/helmet.ts +++ b/src/app/loaders/express/helmet.ts @@ -1,10 +1,9 @@ import { RequestHandler } from 'express' import helmet from 'helmet' import { ContentSecurityPolicyOptions } from 'helmet/dist/middlewares/content-security-policy' -import { get } from 'lodash' import config from '../../config/config' -import featureManager, { FeatureNames } from '../../config/feature-manager' +import { sentryConfig } from '../../config/feature-manager/sentry.config' const helmetMiddlewares = () => { // Only add the "Strict-Transport-Security" header if request is https. @@ -79,11 +78,7 @@ const helmetMiddlewares = () => { formAction: ["'self'"], } - const reportUri = get( - featureManager.props(FeatureNames.Sentry), - 'cspReportUri', - undefined, - ) + const reportUri = sentryConfig.cspReportUri const cspOptionalDirectives: ContentSecurityPolicyOptions['directives'] = {} diff --git a/src/app/loaders/express/locals.ts b/src/app/loaders/express/locals.ts index a3e5fe77f2..550d5eef4f 100644 --- a/src/app/loaders/express/locals.ts +++ b/src/app/loaders/express/locals.ts @@ -3,6 +3,8 @@ import { get } from 'lodash' import config from '../../config/config' import featureManager, { FeatureNames } from '../../config/feature-manager' +import { googleAnalyticsConfig } from '../../config/feature-manager/google-analytics.config' +import { sentryConfig } from '../../config/feature-manager/sentry.config' // Construct js with environment variables needed by frontend const frontendVars = { @@ -17,11 +19,7 @@ const frontendVars = { 'captchaPublicKey', null, ), // Recaptcha - sentryConfigUrl: get( - featureManager.props(FeatureNames.Sentry), - 'sentryConfigUrl', - null, - ), // Sentry.IO + sentryConfigUrl: sentryConfig.sentryConfigUrl, // Sentry.IO isSPMaintenance: get( featureManager.props(FeatureNames.SpcpMyInfo), 'isSPMaintenance', @@ -32,11 +30,7 @@ const frontendVars = { 'isCPMaintenance', null, ), // Corppass maintenance message - GATrackingID: get( - featureManager.props(FeatureNames.GoogleAnalytics), - 'GATrackingID', - null, - ), + GATrackingID: googleAnalyticsConfig.GATrackingID, spcpCookieDomain: get( featureManager.props(FeatureNames.SpcpMyInfo), 'spcpCookieDomain', diff --git a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts index f48656cb2f..4d554fe663 100644 --- a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts +++ b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts @@ -4,12 +4,11 @@ import moment from 'moment-timezone' import mongoose from 'mongoose' import getSubmissionModel, { + getEmailSubmissionModel, getEncryptSubmissionModel, } from 'src/app/models/submission.server.model' import { - IEmailSubmissionSchema, IEncryptedSubmissionSchema, - ISubmissionSchema, SubmissionMetadata, SubmissionType, } from 'src/types' @@ -17,6 +16,7 @@ import { import dbHandler from 'tests/unit/backend/helpers/jest-db' const Submission = getSubmissionModel(mongoose) +const EmailSubmission = getEmailSubmissionModel(mongoose) const EncryptSubmission = getEncryptSubmissionModel(mongoose) describe('Encrypt Submission Model', () => { @@ -33,15 +33,14 @@ describe('Encrypt Submission Model', () => { const validFormId = new ObjectId().toHexString() const createdDate = new Date() // Add valid encrypt submission. - const validSubmission = - await Submission.create({ - form: validFormId, - myInfoFields: [], - submissionType: SubmissionType.Encrypt, - encryptedContent: MOCK_ENCRYPTED_CONTENT, - version: 1, - created: createdDate, - }) + const validSubmission = await EncryptSubmission.create({ + form: validFormId, + myInfoFields: [], + submissionType: SubmissionType.Encrypt, + encryptedContent: MOCK_ENCRYPTED_CONTENT, + version: 1, + created: createdDate, + }) // Act const result = await EncryptSubmission.findSingleMetadata( @@ -110,7 +109,7 @@ describe('Encrypt Submission Model', () => { // Arrange // Add 3 valid encrypt submission. const validSubmissionPromises = times(3, (idx) => - Submission.create({ + EncryptSubmission.create({ form: VALID_FORM_ID, myInfoFields: [], submissionType: SubmissionType.Encrypt, @@ -119,9 +118,8 @@ describe('Encrypt Submission Model', () => { created: MOCK_CREATED_DATES_ASC[idx], }), ) - const validSubmissions: ISubmissionSchema[] = await Promise.all( - validSubmissionPromises, - ) + const validSubmissions: IEncryptedSubmissionSchema[] = + await Promise.all(validSubmissionPromises) // Act const actual = await EncryptSubmission.findAllMetadataByFormId( @@ -149,7 +147,7 @@ describe('Encrypt Submission Model', () => { // Arrange // Add 3 valid encrypt submission. const validSubmissionPromises = times(3, (idx) => - Submission.create({ + EncryptSubmission.create({ form: VALID_FORM_ID, myInfoFields: [], submissionType: SubmissionType.Encrypt, @@ -158,9 +156,8 @@ describe('Encrypt Submission Model', () => { created: MOCK_CREATED_DATES_ASC[idx], }), ) - const validSubmissions: ISubmissionSchema[] = await Promise.all( - validSubmissionPromises, - ) + const validSubmissions: IEncryptedSubmissionSchema[] = + await Promise.all(validSubmissionPromises) // Act const actual = await EncryptSubmission.findAllMetadataByFormId( @@ -192,7 +189,7 @@ describe('Encrypt Submission Model', () => { // Arrange // Add 3 valid encrypt submission. const validSubmissionPromises = times(3, (idx) => - Submission.create({ + EncryptSubmission.create({ form: VALID_FORM_ID, myInfoFields: [], submissionType: SubmissionType.Encrypt, @@ -201,9 +198,8 @@ describe('Encrypt Submission Model', () => { created: MOCK_CREATED_DATES_ASC[idx], }), ) - const validSubmissions: ISubmissionSchema[] = await Promise.all( - validSubmissionPromises, - ) + const validSubmissions: IEncryptedSubmissionSchema[] = + await Promise.all(validSubmissionPromises) // Act const actual = await EncryptSubmission.findAllMetadataByFormId( @@ -235,7 +231,7 @@ describe('Encrypt Submission Model', () => { // Arrange // Add 3 valid encrypt submission. const validSubmissionPromises = times(3, (idx) => - Submission.create({ + EncryptSubmission.create({ form: VALID_FORM_ID, myInfoFields: [], submissionType: SubmissionType.Encrypt, @@ -244,9 +240,8 @@ describe('Encrypt Submission Model', () => { created: MOCK_CREATED_DATES_ASC[idx], }), ) - const validSubmissions: ISubmissionSchema[] = await Promise.all( - validSubmissionPromises, - ) + const validSubmissions: IEncryptedSubmissionSchema[] = + await Promise.all(validSubmissionPromises) // Act const actual = await EncryptSubmission.findAllMetadataByFormId( @@ -288,11 +283,11 @@ describe('Encrypt Submission Model', () => { it('should return cursor that contains all the submissions', async () => { // Arrange const validFormId = new ObjectId().toHexString() - const validSubmission = await Submission.create({ + const validSubmission = await EncryptSubmission.create({ submissionType: SubmissionType.Encrypt, form: validFormId, encryptedContent: 'mock encrypted content abc', - version: 1, + version: 3, }) const expectedSubmission = pick( validSubmission, @@ -301,6 +296,7 @@ describe('Encrypt Submission Model', () => { 'verifiedContent', 'encryptedContent', 'submissionType', + 'version', ) // Act @@ -344,11 +340,11 @@ describe('Encrypt Submission Model', () => { it('should return correct submission by its id', async () => { // Arrange const validFormId = new ObjectId().toHexString() - const validSubmission = await Submission.create({ + const validSubmission = await EncryptSubmission.create({ submissionType: SubmissionType.Encrypt, form: validFormId, encryptedContent: 'mock encrypted content abc', - version: 1, + version: 33, attachmentMetadata: { someFileName: 'some url of attachment' }, }) @@ -360,15 +356,16 @@ describe('Encrypt Submission Model', () => { // Assert const expected = pick( - validSubmission.toObject(), + validSubmission.toJSON(), '_id', 'attachmentMetadata', 'created', 'encryptedContent', 'submissionType', + 'version', ) expect(actual).not.toBeNull() - expect(actual?.toObject()).toEqual(expected) + expect(actual?.toJSON()).toEqual(expected) }) it('should return null when submission id does not exist', async () => { @@ -390,14 +387,13 @@ describe('Encrypt Submission Model', () => { it('should return null when type of submission with given id is not SubmissionType.Encrypt', async () => { // Arrange const validFormId = new ObjectId().toHexString() - const validEmailSubmission = - await Submission.create({ - submissionType: SubmissionType.Email, - form: validFormId, - recipientEmails: ['any@example.com'], - responseHash: 'any hash', - responseSalt: 'any salt', - }) + const validEmailSubmission = await EmailSubmission.create({ + submissionType: SubmissionType.Email, + form: validFormId, + recipientEmails: ['any@example.com'], + responseHash: 'any hash', + responseSalt: 'any salt', + }) // Act const actual = await EncryptSubmission.findEncryptedSubmissionById( diff --git a/src/app/models/__tests__/submission.server.model.spec.ts b/src/app/models/__tests__/submission.server.model.spec.ts index f726745928..8ee24f5d5d 100644 --- a/src/app/models/__tests__/submission.server.model.spec.ts +++ b/src/app/models/__tests__/submission.server.model.spec.ts @@ -68,6 +68,7 @@ describe('Submission Model', () => { isRetryEnabled: true, webhookView: { data: { + attachmentDownloadUrls: {}, formId: String(form._id), submissionId: String(submission._id), encryptedContent: MOCK_ENCRYPTED_CONTENT, @@ -120,6 +121,7 @@ describe('Submission Model', () => { isRetryEnabled: false, webhookView: { data: { + attachmentDownloadUrls: {}, formId: String(form._id), submissionId: String(submission._id), encryptedContent: MOCK_ENCRYPTED_CONTENT, @@ -155,6 +157,7 @@ describe('Submission Model', () => { isRetryEnabled: false, webhookView: { data: { + attachmentDownloadUrls: {}, formId: String(form._id), submissionId: String(submission._id), encryptedContent: MOCK_ENCRYPTED_CONTENT, @@ -268,6 +271,7 @@ describe('Submission Model', () => { created: expect.any(Date), encryptedContent: MOCK_ENCRYPTED_CONTENT, verifiedContent: undefined, + attachmentDownloadUrls: {}, version: 1, }, }) @@ -294,6 +298,7 @@ describe('Submission Model', () => { // Assert expect(actualWebhookView).toEqual({ data: { + attachmentDownloadUrls: {}, formId: expect.any(String), submissionId: expect.any(String), created: expect.any(Date), @@ -336,6 +341,7 @@ describe('Submission Model', () => { // Assert expect(actualWebhookView).toEqual({ data: { + attachmentDownloadUrls: {}, formId: expect.any(String), submissionId: expect.any(String), created: expect.any(Date), diff --git a/src/app/models/field/attachmentField.ts b/src/app/models/field/attachmentField.ts index 49ee6eadfe..80fe25ca69 100644 --- a/src/app/models/field/attachmentField.ts +++ b/src/app/models/field/attachmentField.ts @@ -1,11 +1,6 @@ import { Document, Schema } from 'mongoose' -import { - AttachmentSize, - IAttachmentField, - IFormSchema, - ResponseMode, -} from '../../../types' +import { AttachmentSize, IAttachmentField, IFormSchema } from '../../../types' // Manual override since mongoose types don't have generics yet. interface IAttachmentFieldSchema extends IAttachmentField, Document { @@ -24,22 +19,6 @@ const createAttachmentFieldSchema = () => { }, }) - // Prevent attachments from being saved on a webhooked form. - AttachmentFieldSchema.pre( - 'validate', - function (next) { - const { webhook, responseMode } = this.parent() - - if (responseMode === ResponseMode.Encrypt && webhook?.url) { - return next( - Error('Attachments are not allowed when a form has a webhook url'), - ) - } - - return next() - }, - ) - return AttachmentFieldSchema } diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index 2e37daae76..7f96c38887 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -187,6 +187,10 @@ EncryptSubmissionSchema.methods.getWebhookView = function ( const formId = this.populated('form') ? String(this.form._id) : String(this.form) + const attachmentRecords = Object.fromEntries( + this.attachmentMetadata ?? new Map(), + ) + const webhookData: WebhookData = { formId, submissionId: String(this._id), @@ -194,6 +198,7 @@ EncryptSubmissionSchema.methods.getWebhookView = function ( verifiedContent: this.verifiedContent, version: this.version, created: this.created, + attachmentDownloadUrls: attachmentRecords, } return { @@ -355,6 +360,7 @@ EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = function ( verifiedContent: 1, attachmentMetadata: 1, created: 1, + version: 1, id: 1, }) .batchSize(2000) @@ -379,6 +385,7 @@ EncryptSubmissionSchema.statics.findEncryptedSubmissionById = function ( verifiedContent: 1, attachmentMetadata: 1, created: 1, + version: 1, }) .exec() } diff --git a/src/app/modules/auth/__tests__/auth.controller.spec.ts b/src/app/modules/auth/__tests__/auth.controller.spec.ts index 94f7d8fe05..b75a6d001d 100644 --- a/src/app/modules/auth/__tests__/auth.controller.spec.ts +++ b/src/app/modules/auth/__tests__/auth.controller.spec.ts @@ -187,7 +187,7 @@ describe('auth.controller', () => { // Assert expect(mockRes.status).toBeCalledWith(200) - expect(mockRes.json).toBeCalledWith(mockUser.toObject()) + expect(mockRes.json).toBeCalledWith(mockUser) }) it('should return 401 when retrieving agency returns InvalidDomainError', async () => { diff --git a/src/app/modules/auth/__tests__/auth.routes.spec.ts b/src/app/modules/auth/__tests__/auth.routes.spec.ts index 66d44b6430..e5d3749cd3 100644 --- a/src/app/modules/auth/__tests__/auth.routes.spec.ts +++ b/src/app/modules/auth/__tests__/auth.routes.spec.ts @@ -97,6 +97,23 @@ describe('auth.routes', () => { expect(response.text).toEqual('OK') }) + it('should return 200 when domain of body.email has a case-insensitive match in Agency collection', async () => { + // Arrange + // Insert agency + const validDomain = 'example.com' + const validEmail = `test@${validDomain}` + await dbHandler.insertAgency({ mailDomain: validDomain }) + + // Act + const response = await request + .post('/auth/checkuser') + .send({ email: validEmail.toUpperCase() }) + + // Assert + expect(response.status).toEqual(200) + expect(response.text).toEqual('OK') + }) + it('should return 500 when validating domain returns a database error', async () => { // Arrange // Insert agency @@ -251,6 +268,23 @@ describe('auth.routes', () => { expect(response.status).toEqual(200) expect(response.body).toEqual(`OTP sent to ${VALID_EMAIL}`) }) + + it('should return 200 when otp is sent successfully and email is non-lowercase', async () => { + // Arrange + const sendLoginOtpSpy = jest + .spyOn(MailService, 'sendLoginOtp') + .mockReturnValueOnce(okAsync(true)) + + // Act + const response = await request + .post('/auth/sendotp') + .send({ email: VALID_EMAIL.toUpperCase() }) + + // Assert + expect(sendLoginOtpSpy).toHaveBeenCalled() + expect(response.status).toEqual(200) + expect(response.body).toEqual(`OTP sent to ${VALID_EMAIL}`) + }) }) describe('POST /auth/verifyotp', () => { @@ -476,6 +510,33 @@ describe('auth.routes', () => { expect(sessionCookie).toBeDefined() }) + it('should return 200 with user object when body.otp is a valid OTP and body.email is non-lowercase', async () => { + // Arrange + // Request for OTP so the hash exists. + await requestForOtp(VALID_EMAIL) + + // Act + const response = await request + .post('/auth/verifyotp') + .send({ email: VALID_EMAIL.toUpperCase(), otp: MOCK_VALID_OTP }) + + // Assert + expect(response.status).toEqual(200) + // Body should be an user object. + expect(response.body).toMatchObject({ + // Required since that's how the data is sent out from the application. + agency: jsonParseStringify(defaultAgency.toObject()), + _id: expect.any(String), + created: expect.any(String), + email: VALID_EMAIL, + }) + // Should have session cookie returned. + const sessionCookie = request.cookies.find( + (cookie) => cookie.name === 'connect.sid', + ) + expect(sessionCookie).toBeDefined() + }) + it('should return 500 when upserting user document fails', async () => { // Arrange // Request for OTP so the hash exists. diff --git a/src/app/modules/auth/auth.controller.ts b/src/app/modules/auth/auth.controller.ts index 1365dace36..81b2993b79 100644 --- a/src/app/modules/auth/auth.controller.ts +++ b/src/app/modules/auth/auth.controller.ts @@ -158,16 +158,15 @@ export const handleLoginVerifyOtp: ControllerHandler< .json(coreErrorMessage) } - // TODO(#212): Should store only userId in session. // Add user info to session. - const userObj = user.toObject() as SessionUser - req.session.user = userObj + const { _id } = user.toObject() as SessionUser + req.session.user = { _id } logger.info({ - message: `Successfully logged in user ${user.email}`, + message: `Successfully logged in user ${user._id}`, meta: logMeta, }) - return res.status(StatusCodes.OK).json(userObj) + return res.status(StatusCodes.OK).json(user) }) // Step 3b: Error occured in one of the steps. .mapErr((error) => { diff --git a/src/app/modules/auth/auth.middlewares.ts b/src/app/modules/auth/auth.middlewares.ts index 3c7b8a0010..0e4650eb1e 100644 --- a/src/app/modules/auth/auth.middlewares.ts +++ b/src/app/modules/auth/auth.middlewares.ts @@ -3,7 +3,6 @@ import { StatusCodes } from 'http-status-codes' import { createLoggerWithLabel } from '../../config/logger' import { createReqMeta } from '../../utils/request' import { ControllerHandler } from '../core/core.types' -import * as UserService from '../user/user.service' import { isUserInSession } from './auth.utils' @@ -25,39 +24,6 @@ export const withUserAuthentication: ControllerHandler = (req, res, next) => { .json({ message: 'User is unauthorized.' }) } -const DENIED_DOMAINS = ['myrp.edu.sg', 'ichat.sp.edu.sg'] - -/** - * If user is from a domain which should not have been whitelisted, - * do not allow any updates. Only allow GET requests, eg to access - * submissions. - * @returns 400 if user in session is from a disallowed domain and - * HTTP method changes database state; next otherwise - */ -export const denyRpSpStudentEmails: ControllerHandler = async ( - req, - res, - next, -) => { - const userId = (req.session as Express.AuthedSession).user._id - return UserService.findUserById(userId) - .map((user) => { - const emailDomain = user.email.split('@').pop() ?? '' - if ( - DENIED_DOMAINS.includes(emailDomain.toLowerCase()) && - req.method.toLowerCase() !== 'get' - ) { - return res.sendStatus(StatusCodes.BAD_REQUEST) - } - return next() - }) - .mapErr(() => - res - .status(StatusCodes.UNPROCESSABLE_ENTITY) - .json({ message: 'User not found' }), - ) -} - /** * Logs all admin actions which change database state (i.e. non-GET requests) * @returns next diff --git a/src/app/modules/auth/auth.routes.ts b/src/app/modules/auth/auth.routes.ts index bd1d343669..69e669fb73 100644 --- a/src/app/modules/auth/auth.routes.ts +++ b/src/app/modules/auth/auth.routes.ts @@ -23,7 +23,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), }), }), AuthController.handleCheckUser, @@ -49,7 +50,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), }), }), AuthController.handleLoginSendOtp, @@ -75,7 +77,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), otp: Joi.string() .required() .regex(/^\d{6}$/) diff --git a/src/app/modules/auth/auth.utils.ts b/src/app/modules/auth/auth.utils.ts index b6de7e1d51..829cb8d7f3 100644 --- a/src/app/modules/auth/auth.utils.ts +++ b/src/app/modules/auth/auth.utils.ts @@ -57,7 +57,6 @@ export const isUserInSession = ( return !!session?.user?._id } -// TODO(#212): Save userId instead of entire user collection in session. export const getUserIdFromSession = ( session?: Express.Session, ): string | undefined => { diff --git a/src/app/modules/billing/billing.controller.ts b/src/app/modules/billing/billing.controller.ts index 23c24fce8c..959b00035a 100644 --- a/src/app/modules/billing/billing.controller.ts +++ b/src/app/modules/billing/billing.controller.ts @@ -55,7 +55,7 @@ export const handleGetBillInfo: ControllerHandler< // Retrieved login stats successfully. logger.info({ - message: `Billing search for ${esrvcId} by ${authedUser.email}`, + message: `Billing search for ${esrvcId} by ${authedUser._id}`, meta: { action: 'handleGetBillInfo', ...createReqMeta(req), diff --git a/src/app/modules/core/core.errors.ts b/src/app/modules/core/core.errors.ts index 9bf9e2ae45..51090f54b1 100644 --- a/src/app/modules/core/core.errors.ts +++ b/src/app/modules/core/core.errors.ts @@ -82,3 +82,13 @@ export class AttachmentUploadError extends ApplicationError { super(message) } } + +/** + * A custom error class returned when a method explicitly returns a list of errors + * but the list itself is empty. + */ +export class EmptyErrorFieldError extends ApplicationError { + constructor(message = 'Errors were returned but list is empty.') { + super(message) + } +} diff --git a/src/app/modules/examples/__tests__/examples.controller.spec.ts b/src/app/modules/examples/__tests__/examples.controller.spec.ts index 5e1efc357b..1d60951145 100644 --- a/src/app/modules/examples/__tests__/examples.controller.spec.ts +++ b/src/app/modules/examples/__tests__/examples.controller.spec.ts @@ -8,11 +8,11 @@ import expressHandler from 'tests/unit/backend/helpers/jest-express' import { DatabaseError } from '../../core/core.errors' import * as ExamplesController from '../examples.controller' import { ResultsNotFoundError } from '../examples.errors' -import { ExamplesFactory } from '../examples.factory' +import * as ExamplesService from '../examples.service' import { ExamplesQueryParams, SingleFormResult } from '../examples.types' -jest.mock('../examples.factory') -const MockExamplesFactory = mocked(ExamplesFactory) +jest.mock('../examples.service') +const MockExamplesService = mocked(ExamplesService) describe('examples.controller', () => { beforeEach(() => jest.clearAllMocks()) @@ -38,7 +38,7 @@ describe('examples.controller', () => { forms: [], totalNumResults: 0, } - MockExamplesFactory.getExampleForms.mockReturnValueOnce( + MockExamplesService.getExampleForms.mockReturnValueOnce( okAsync(mockResult), ) @@ -46,7 +46,7 @@ describe('examples.controller', () => { await ExamplesController.handleGetExamples(MOCK_REQ, mockRes, jest.fn()) // Assert - expect(MockExamplesFactory.getExampleForms).toHaveBeenCalledWith( + expect(MockExamplesService.getExampleForms).toHaveBeenCalledWith( MOCK_REQ_QUERY, ) expect(mockRes.status).toBeCalledWith(200) @@ -57,7 +57,7 @@ describe('examples.controller', () => { // Arrange const mockRes = expressHandler.mockResponse() // Mock getExampleForms to return error. - MockExamplesFactory.getExampleForms.mockReturnValueOnce( + MockExamplesService.getExampleForms.mockReturnValueOnce( errAsync(new DatabaseError()), ) @@ -65,7 +65,7 @@ describe('examples.controller', () => { await ExamplesController.handleGetExamples(MOCK_REQ, mockRes, jest.fn()) // Assert - expect(MockExamplesFactory.getExampleForms).toHaveBeenCalledWith( + expect(MockExamplesService.getExampleForms).toHaveBeenCalledWith( MOCK_REQ_QUERY, ) expect(mockRes.status).toBeCalledWith(500) @@ -106,7 +106,7 @@ describe('examples.controller', () => { title: 'mockTitle', }, } - MockExamplesFactory.getSingleExampleForm.mockReturnValueOnce( + MockExamplesService.getSingleExampleForm.mockReturnValueOnce( okAsync(mockResult), ) @@ -118,7 +118,7 @@ describe('examples.controller', () => { ) // Assert - expect(MockExamplesFactory.getSingleExampleForm).toHaveBeenCalledWith( + expect(MockExamplesService.getSingleExampleForm).toHaveBeenCalledWith( MOCK_REQ_PARAMS.formId, ) expect(mockRes.status).toBeCalledWith(200) @@ -130,7 +130,7 @@ describe('examples.controller', () => { const mockRes = expressHandler.mockResponse() // Mock getSingleExampleForm to return not found error. const mockErrorString = 'not found error!' - MockExamplesFactory.getSingleExampleForm.mockReturnValueOnce( + MockExamplesService.getSingleExampleForm.mockReturnValueOnce( errAsync(new ResultsNotFoundError(mockErrorString)), ) @@ -142,7 +142,7 @@ describe('examples.controller', () => { ) // Assert - expect(MockExamplesFactory.getSingleExampleForm).toHaveBeenCalledWith( + expect(MockExamplesService.getSingleExampleForm).toHaveBeenCalledWith( MOCK_REQ_PARAMS.formId, ) expect(mockRes.status).toBeCalledWith(404) @@ -154,7 +154,7 @@ describe('examples.controller', () => { const mockRes = expressHandler.mockResponse() // Mock getSingleExampleForm to return database error. const mockErrorString = 'database error!' - MockExamplesFactory.getSingleExampleForm.mockReturnValueOnce( + MockExamplesService.getSingleExampleForm.mockReturnValueOnce( errAsync(new DatabaseError(mockErrorString)), ) @@ -166,7 +166,7 @@ describe('examples.controller', () => { ) // Assert - expect(MockExamplesFactory.getSingleExampleForm).toHaveBeenCalledWith( + expect(MockExamplesService.getSingleExampleForm).toHaveBeenCalledWith( MOCK_REQ_PARAMS.formId, ) expect(mockRes.status).toBeCalledWith(500) diff --git a/src/app/modules/examples/__tests__/examples.factory.spec.ts b/src/app/modules/examples/__tests__/examples.factory.spec.ts deleted file mode 100644 index 4df4721198..0000000000 --- a/src/app/modules/examples/__tests__/examples.factory.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { mocked } from 'ts-jest/utils' - -import { - FeatureNames, - IAggregateStats, - RegisteredFeature, -} from 'src/app/config/feature-manager' - -import { createExamplesFactory } from '../examples.factory' -import * as ExamplesService from '../examples.service' -import { ExamplesQueryParams, RetrievalType } from '../examples.types' - -// Higher order function requires two level mocking. -jest.mock('../examples.service', () => ({ - getExampleForms: jest.fn().mockImplementation(() => jest.fn()), - getSingleExampleForm: jest.fn().mockImplementation(() => jest.fn()), -})) - -const MockExamplesService = mocked(ExamplesService) - -describe('examples.factory', () => { - describe('aggregate-stats feature disabled', () => { - const MOCK_DISABLED_FEATURE: RegisteredFeature = - { - isEnabled: false, - props: {} as IAggregateStats, - } - const ExamplesFactory = createExamplesFactory(MOCK_DISABLED_FEATURE) - - describe('getExampleForms', () => { - it('should invoke service method with RetrievalType.Submissions', async () => { - // Act - await ExamplesFactory.getExampleForms({} as ExamplesQueryParams) - - // Assert - expect(MockExamplesService.getExampleForms).toHaveBeenCalledWith( - RetrievalType.Submissions, - ) - }) - }) - - describe('getSingleExampleForm', () => { - it('should invoke service method with RetrievalType.Submissions', async () => { - // Act - await ExamplesFactory.getSingleExampleForm('anything') - - // Assert - expect(MockExamplesService.getSingleExampleForm).toHaveBeenCalledWith( - RetrievalType.Submissions, - ) - }) - }) - }) - - describe('aggregate-stats feature enabled', () => { - const MOCK_ENABLED_FEATURE: RegisteredFeature = - { - isEnabled: true, - props: {} as IAggregateStats, - } - const ExamplesFactory = createExamplesFactory(MOCK_ENABLED_FEATURE) - - describe('getExampleForms', () => { - it('should invoke service method with RetrievalType.Stats', async () => { - // Act - await ExamplesFactory.getExampleForms({} as ExamplesQueryParams) - - // Assert - expect(MockExamplesService.getExampleForms).toHaveBeenCalledWith( - RetrievalType.Stats, - ) - }) - }) - - describe('getSingleExampleForm', () => { - it('should invoke service method with RetrievalType.Stats', async () => { - // Act - await ExamplesFactory.getSingleExampleForm('anything') - - // Assert - expect(MockExamplesService.getSingleExampleForm).toHaveBeenCalledWith( - RetrievalType.Stats, - ) - }) - }) - }) -}) diff --git a/src/app/modules/examples/__tests__/examples.routes.spec.ts b/src/app/modules/examples/__tests__/examples.routes.spec.ts index d59e4224ca..13f9a1a4f4 100644 --- a/src/app/modules/examples/__tests__/examples.routes.spec.ts +++ b/src/app/modules/examples/__tests__/examples.routes.spec.ts @@ -2,7 +2,6 @@ import { ObjectId } from 'bson-ext' import { keyBy } from 'lodash' import { errAsync } from 'neverthrow' import supertest, { Session } from 'supertest-session' -import { mocked } from 'ts-jest/utils' import { IAgencySchema, IUserSchema } from 'src/types' @@ -12,10 +11,9 @@ import { buildCelebrateError } from 'tests/unit/backend/helpers/celebrate' import dbHandler from 'tests/unit/backend/helpers/jest-db' import { DatabaseError } from '../../core/core.errors' -import { ExamplesFactory } from '../examples.factory' import { ExamplesRouter } from '../examples.routes' -import { getExampleForms, getSingleExampleForm } from '../examples.service' -import { FormInfo, RetrievalType } from '../examples.types' +import * as ExamplesService from '../examples.service' +import { FormInfo } from '../examples.types' import prepareTestData, { SearchTerm, @@ -28,9 +26,6 @@ jest.mock('../examples.constants', () => ({ MIN_SUB_COUNT: 0, })) -jest.mock('../examples.factory') -const MockExamplesFactory = mocked(ExamplesFactory) - const app = setupApp('/examples', ExamplesRouter, { setupWithAuth: true, }) @@ -71,326 +66,157 @@ describe('examples.routes', () => { afterAll(async () => await dbHandler.closeDatabase()) describe('GET /examples', () => { - describe('AggregateStats feature enabled', () => { - beforeAll(() => { - MockExamplesFactory.getExampleForms.mockImplementation( - getExampleForms(RetrievalType.Stats), - ) - }) + it('should return 200 with array of example forms for all agencies when query.agency is missing', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) - it('should return 200 with array of example forms for all agencies when query.agency is missing', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - }) - - // Assert - // Should have both comTestData and orgTestData since searching by all - // agencies. - const expectedBody = keyBy( - [ - ...stringifyFormInfoArray(comTestData.total.expectedFormInfo), - ...stringifyFormInfoArray(orgTestData.total.expectedFormInfo), - ], - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - expect(response.status).toEqual(200) - // Check shape. - expect(response.body).toEqual({ - forms: expect.any(Array), - }) - expect(actualBody).toEqual(expectedBody) + // Act + const response = await session.get('/examples').query({ + pageNo: '0', }) - it('should return 200 with array of example forms for a particular agency when query.agency is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: orgAgency._id.toString(), - }) - - // Assert - // Should only have orgTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(orgTestData.total.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - }) - expect(actualBody).toEqual(expectedBody) + // Assert + // Should have both comTestData and orgTestData since searching by all + // agencies. + const expectedBody = keyBy( + [ + ...stringifyFormInfoArray(comTestData.total.expectedFormInfo), + ...stringifyFormInfoArray(orgTestData.total.expectedFormInfo), + ], + '_id', + ) + const actualBody = keyBy(response.body.forms, '_id') + expect(response.status).toEqual(200) + // Check shape. + expect(response.body).toEqual({ + forms: expect.any(Array), }) + expect(actualBody).toEqual(expectedBody) + }) - it('should return 200 with empty array when no forms match the criterias', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: new ObjectId().toHexString(), - }) - - // Assert - expect(response.status).toEqual(200) - // Check shape. - expect(response.body).toEqual({ - forms: [], - }) + it('should return 200 with array of example forms for a particular agency when query.agency is provided', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/examples').query({ + pageNo: '0', + agency: orgAgency._id.toString(), }) - it('should return 200 with array of example forms that match given query.searchTerm when that is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: comAgency._id.toString(), - shouldGetTotalNumResults: 'true', - searchTerm: SearchTerm.First, - }) - - // Assert - // Should only have comTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.first.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: comTestData.first.formCount, - }) - expect(actualBody).toEqual(expectedBody) + // Assert + // Should only have orgTestData since its agency id is provided. + const expectedBody = keyBy( + stringifyFormInfoArray(orgTestData.total.expectedFormInfo), + '_id', + ) + const actualBody = keyBy(response.body.forms, '_id') + // Check shape. + expect(response.status).toEqual(200) + expect(response.body).toEqual({ + forms: expect.any(Array), }) + expect(actualBody).toEqual(expectedBody) + }) - it('should return 200 with correctly offset example forms according to query.pageNo when that is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '1', - shouldGetTotalNumResults: 'true', - }) - - // Assert - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: - comTestData.total.formCount + orgTestData.total.formCount, - }) - // Should have nothing since the number of results is less than the - // offset. - expect(actualBody).toEqual({}) + it('should return 200 with empty array when no forms match the criterias', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/examples').query({ + pageNo: '0', + agency: new ObjectId().toHexString(), }) - it('should return 200 with array of example forms and total count when query.shouldGetTotalNumResults is true', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: comAgency._id.toString(), - shouldGetTotalNumResults: 'true', - }) - - // Assert - // Should only have comTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.total.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: comTestData.total.formCount, - }) - expect(actualBody).toEqual(expectedBody) + // Assert + expect(response.status).toEqual(200) + // Check shape. + expect(response.body).toEqual({ + forms: [], }) }) - describe('AggregateStats feature disabled', () => { - beforeAll(() => { - MockExamplesFactory.getExampleForms.mockImplementation( - getExampleForms(RetrievalType.Submissions), - ) - }) + it('should return 200 with array of example forms that match given query.searchTerm when that is provided', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) - it('should return 200 with array of example forms for all agencies when query.agency is missing', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - }) - - // Assert - // Should have both comTestData and orgTestData since searching by all - // agencies. - const expectedBody = keyBy( - [ - ...stringifyFormInfoArray(comTestData.total.expectedFormInfo), - ...stringifyFormInfoArray(orgTestData.total.expectedFormInfo), - ], - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - expect(response.status).toEqual(200) - // Check shape. - expect(response.body).toEqual({ - forms: expect.any(Array), - }) - expect(actualBody).toEqual(expectedBody) + // Act + const response = await session.get('/examples').query({ + pageNo: '0', + agency: comAgency._id.toString(), + shouldGetTotalNumResults: 'true', + searchTerm: SearchTerm.First, }) - it('should return 200 with array of example forms for a particular agency when query.agency is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: orgAgency._id.toString(), - }) - - // Assert - // Should only have orgTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(orgTestData.total.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - }) - expect(actualBody).toEqual(expectedBody) + // Assert + // Should only have comTestData since its agency id is provided. + const expectedBody = keyBy( + stringifyFormInfoArray(comTestData.first.expectedFormInfo), + '_id', + ) + const actualBody = keyBy(response.body.forms, '_id') + // Check shape. + expect(response.status).toEqual(200) + expect(response.body).toEqual({ + forms: expect.any(Array), + // Should have total num results key value. + totalNumResults: comTestData.first.formCount, }) + expect(actualBody).toEqual(expectedBody) + }) - it('should return 200 with empty array when no forms match the criterias', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: new ObjectId().toHexString(), - }) - - // Assert - expect(response.status).toEqual(200) - // Check shape. - expect(response.body).toEqual({ - forms: [], - }) + it('should return 200 with correctly offset example forms according to query.pageNo when that is provided', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/examples').query({ + pageNo: '1', + shouldGetTotalNumResults: 'true', }) - it('should return 200 with array of example forms that match given query.searchTerm when that is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: comAgency._id.toString(), - shouldGetTotalNumResults: 'true', - searchTerm: SearchTerm.First, - }) - - // Assert - // Should only have comTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.first.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: comTestData.first.formCount, - }) - expect(actualBody).toEqual(expectedBody) + // Assert + const actualBody = keyBy(response.body.forms, '_id') + // Check shape. + expect(response.status).toEqual(200) + expect(response.body).toEqual({ + forms: expect.any(Array), + // Should have total num results key value. + totalNumResults: + comTestData.total.formCount + orgTestData.total.formCount, }) + // Should have nothing since the number of results is less than the + // offset. + expect(actualBody).toEqual({}) + }) + + it('should return 200 with array of example forms and total count when query.shouldGetTotalNumResults is true', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) - it('should return 200 with correctly offset example forms according to query.pageNo when that is provided', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '1', - shouldGetTotalNumResults: 'true', - }) - - // Assert - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: - comTestData.total.formCount + orgTestData.total.formCount, - }) - // Should have nothing since the number of results is less than the - // offset. - expect(actualBody).toEqual({}) + // Act + const response = await session.get('/examples').query({ + pageNo: '0', + agency: comAgency._id.toString(), + shouldGetTotalNumResults: 'true', }) - it('should return 200 with array of example forms and total count when query.shouldGetTotalNumResults is true', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - - // Act - const response = await session.get('/examples').query({ - pageNo: '0', - agency: comAgency._id.toString(), - shouldGetTotalNumResults: 'true', - }) - - // Assert - // Should only have comTestData since its agency id is provided. - const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.total.expectedFormInfo), - '_id', - ) - const actualBody = keyBy(response.body.forms, '_id') - // Check shape. - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - forms: expect.any(Array), - // Should have total num results key value. - totalNumResults: comTestData.total.formCount, - }) - expect(actualBody).toEqual(expectedBody) + // Assert + // Should only have comTestData since its agency id is provided. + const expectedBody = keyBy( + stringifyFormInfoArray(comTestData.total.expectedFormInfo), + '_id', + ) + const actualBody = keyBy(response.body.forms, '_id') + // Check shape. + expect(response.status).toEqual(200) + expect(response.body).toEqual({ + forms: expect.any(Array), + // Should have total num results key value. + totalNumResults: comTestData.total.formCount, }) + expect(actualBody).toEqual(expectedBody) }) it('should return 400 when query.pageNo is not provided', async () => { @@ -493,7 +319,7 @@ describe('examples.routes', () => { it('should return 500 when an error occurs whilst querying the database', async () => { // Arrange const getExamplesSpy = jest - .spyOn(ExamplesFactory, 'getExampleForms') + .spyOn(ExamplesService, 'getExampleForms') .mockReturnValueOnce(errAsync(new DatabaseError())) const session = await createAuthedSession(defaultUser.email, request) @@ -512,85 +338,37 @@ describe('examples.routes', () => { }) describe('GET /examples/:formId', () => { - describe('AggregateStats feature enabled', () => { - beforeAll(() => { - MockExamplesFactory.getSingleExampleForm.mockImplementation( - getSingleExampleForm(RetrievalType.Stats), - ) - }) - - it('should return 200 with the example information of the retrieved form', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - const validFormId = comTestData.second.forms[0]._id - - // Act - const response = await session.get(`/examples/${validFormId}`) - - // Assert - const expectedFormInfo = stringifyFormInfo( - comTestData.second.expectedFormInfo[0], - ) - - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - form: expectedFormInfo, - }) - }) + it('should return 200 with the example information of the retrieved form', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) + const validFormId = comTestData.second.forms[0]._id - it('should return 404 when the form with the given formId does not exist in the database', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - const randomFormId = new ObjectId().toHexString() + // Act + const response = await session.get(`/examples/${validFormId}`) - // Act - const response = await session.get(`/examples/${randomFormId}`) + // Assert + const expectedFormInfo = stringifyFormInfo( + comTestData.second.expectedFormInfo[0], + ) - // Assert - expect(response.status).toEqual(404) - expect(response.body).toEqual({ - message: 'Error in retrieving template form - form not found.', - }) + expect(response.status).toEqual(200) + expect(response.body).toEqual({ + form: expectedFormInfo, }) }) - describe('AggregateStats feature disabled', () => { - beforeAll(() => { - MockExamplesFactory.getSingleExampleForm.mockImplementation( - getSingleExampleForm(RetrievalType.Submissions), - ) - }) - it('should return 200 with the example information of the retrieved form', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - const validFormId = comTestData.first.forms[1]._id - - // Act - const response = await session.get(`/examples/${validFormId}`) - - // Assert - const expectedFormInfo = stringifyFormInfo( - comTestData.first.expectedFormInfo[1], - ) - expect(response.status).toEqual(200) - expect(response.body).toEqual({ - form: expectedFormInfo, - }) - }) - - it('should return 404 when the form with the given formId does not exist in the database', async () => { - // Arrange - const session = await createAuthedSession(defaultUser.email, request) - const randomFormId = new ObjectId().toHexString() + it('should return 404 when the form with the given formId does not exist in the database', async () => { + // Arrange + const session = await createAuthedSession(defaultUser.email, request) + const randomFormId = new ObjectId().toHexString() - // Act - const response = await session.get(`/examples/${randomFormId}`) + // Act + const response = await session.get(`/examples/${randomFormId}`) - // Assert - expect(response.status).toEqual(404) - expect(response.body).toEqual({ - message: 'Error in retrieving template form - form not found.', - }) + // Assert + expect(response.status).toEqual(404) + expect(response.body).toEqual({ + message: 'Error in retrieving template form - form not found.', }) }) @@ -611,9 +389,9 @@ describe('examples.routes', () => { const session = await createAuthedSession(defaultUser.email, request) const validFormId = comTestData.first.forms[0]._id const mockErrorString = 'database error' - MockExamplesFactory.getSingleExampleForm.mockReturnValueOnce( - errAsync(new DatabaseError(mockErrorString)), - ) + jest + .spyOn(ExamplesService, 'getSingleExampleForm') + .mockReturnValueOnce(errAsync(new DatabaseError(mockErrorString))) // Act const response = await session.get(`/examples/${validFormId}`) diff --git a/src/app/modules/examples/__tests__/examples.service.spec.ts b/src/app/modules/examples/__tests__/examples.service.spec.ts index 1a7f307f89..35b7783009 100644 --- a/src/app/modules/examples/__tests__/examples.service.spec.ts +++ b/src/app/modules/examples/__tests__/examples.service.spec.ts @@ -2,19 +2,16 @@ import { ObjectId } from 'bson-ext' import mongoose from 'mongoose' import getFormStatisticsTotalModel from 'src/app/models/form_statistics_total.server.model' -import getSubmissionModel from 'src/app/models/submission.server.model' import dbHandler from 'tests/unit/backend/helpers/jest-db' import { PAGE_SIZE } from '../examples.constants' import { ResultsNotFoundError } from '../examples.errors' import * as ExamplesService from '../examples.service' -import { RetrievalType } from '../examples.types' import prepareTestData, { TestData } from './helpers/prepareTestData' const FormStatsModel = getFormStatisticsTotalModel(mongoose) -const SubmissionModel = getSubmissionModel(mongoose) // Mock min sub count so anything above 0 submissions will be counted. jest.mock('../examples.constants', () => ({ @@ -34,290 +31,139 @@ describe('examples.service', () => { afterAll(async () => await dbHandler.closeDatabase()) describe('getExampleForms', () => { - describe('with RetrievalType.Stats', () => { - const getExampleFormsUsingStats = ExamplesService.getExampleForms( - RetrievalType.Stats, - ) - - describe('when query.searchTerm exists', () => { - describe('when query.shouldGetTotalNumResults is true', () => { - it('should return list of form info that match the search term with results count', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - searchTerm: testData.second.searchTerm, - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - totalNumResults: testData.second.formCount, - forms: expect.arrayContaining(testData.second.expectedFormInfo), - }) - }) - - it('should return empty list if no forms match search term with 0 result count', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - searchTerm: INVALID_SEARCH_TERM, - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - totalNumResults: 0, - }) + describe('when query.searchTerm exists', () => { + describe('when query.shouldGetTotalNumResults is true', () => { + it('should return list of form info that match the search term with results count', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + searchTerm: testData.second.searchTerm, + pageNo: 0, + shouldGetTotalNumResults: true, + }) + + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + totalNumResults: testData.second.formCount, + forms: expect.arrayContaining(testData.second.expectedFormInfo), }) }) - describe('when query.shouldGetTotalNumResults is false', () => { - it('should return only list of form info that match the search term', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - searchTerm: testData.first.searchTerm, - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: expect.arrayContaining(testData.first.expectedFormInfo), - }) + it('should return empty list if no forms match search term with 0 result count', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + searchTerm: INVALID_SEARCH_TERM, + pageNo: 0, + shouldGetTotalNumResults: true, }) - it('should return empty list if no forms match search term', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - searchTerm: INVALID_SEARCH_TERM, - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: [], + totalNumResults: 0, }) }) }) - describe('when query.searchTerm does not exist', () => { - describe('when query.shouldGetTotalNumResults is true', () => { - it('should return list of form info with results count', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - totalNumResults: testData.total.formCount, - forms: expect.arrayContaining(testData.total.expectedFormInfo), - }) + describe('when query.shouldGetTotalNumResults is false', () => { + it('should return only list of form info that match the search term', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + searchTerm: testData.first.searchTerm, + pageNo: 0, + shouldGetTotalNumResults: false, }) - it('should return empty list with number of forms with submissions when offset is more than number of documents in collection', async () => { - // Arrange - const overOffset = - (await FormStatsModel.estimatedDocumentCount()) / PAGE_SIZE + 1 - // Act - const actualResults = await getExampleFormsUsingStats({ - pageNo: overOffset, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - totalNumResults: testData.total.formCount, - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: expect.arrayContaining(testData.first.expectedFormInfo), }) }) - describe('when query.shouldGetTotalNumResults is false', () => { - it('should return list of form info', async () => { - // Act - const actualResults = await getExampleFormsUsingStats({ - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: expect.arrayContaining(testData.total.expectedFormInfo), - }) + it('should return empty list if no forms match search term', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + searchTerm: INVALID_SEARCH_TERM, + pageNo: 0, + shouldGetTotalNumResults: false, }) - it('should return empty list when offset is more than number of documents', async () => { - // Arrange - const overOffset = - (await FormStatsModel.estimatedDocumentCount()) / PAGE_SIZE + 1 - // Act - const actualResults = await getExampleFormsUsingStats({ - pageNo: overOffset, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: [], }) }) }) }) - describe('with RetrievalType.Submissions', () => { - const getExampleFormsUsingSubs = ExamplesService.getExampleForms( - RetrievalType.Submissions, - ) - - describe('when query.searchTerm exists', () => { - describe('when query.shouldGetTotalNumResults is true', () => { - it('should return list of form info that match the search term with results count', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - searchTerm: testData.second.searchTerm, - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - totalNumResults: testData.second.formCount, - forms: expect.arrayContaining(testData.second.expectedFormInfo), - }) + describe('when query.searchTerm does not exist', () => { + describe('when query.shouldGetTotalNumResults is true', () => { + it('should return list of form info with results count', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + pageNo: 0, + shouldGetTotalNumResults: true, }) - it('should return empty list if no forms match search term with 0 result count', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - searchTerm: INVALID_SEARCH_TERM, - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - totalNumResults: 0, - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + totalNumResults: testData.total.formCount, + forms: expect.arrayContaining(testData.total.expectedFormInfo), }) }) - describe('when query.shouldGetTotalNumResults is false', () => { - it('should return only list of form info that match the search term', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - searchTerm: testData.first.searchTerm, - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap().forms).toEqual( - expect.arrayContaining(testData.first.expectedFormInfo), - ) + it('should return empty list with number of forms with submissions when offset is more than number of documents in collection', async () => { + // Arrange + const overOffset = + (await FormStatsModel.estimatedDocumentCount()) / PAGE_SIZE + 1 + // Act + const actualResults = await ExamplesService.getExampleForms({ + pageNo: overOffset, + shouldGetTotalNumResults: true, }) - it('should return empty list if no forms match search term', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - searchTerm: INVALID_SEARCH_TERM, - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: [], + totalNumResults: testData.total.formCount, }) }) }) - describe('when query.searchTerm does not exist', () => { - describe('when query.shouldGetTotalNumResults is true', () => { - it('should return list of form info with results count', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - pageNo: 0, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - totalNumResults: testData.total.formCount, - forms: expect.arrayContaining(testData.total.expectedFormInfo), - }) + describe('when query.shouldGetTotalNumResults is false', () => { + it('should return list of form info', async () => { + // Act + const actualResults = await ExamplesService.getExampleForms({ + pageNo: 0, + shouldGetTotalNumResults: false, }) - it('should return empty list with total number of submissions when offset is more than number of documents in collection', async () => { - // Arrange - const overOffset = - (await SubmissionModel.estimatedDocumentCount()) / PAGE_SIZE + 1 - // Act - const actualResults = await getExampleFormsUsingSubs({ - pageNo: overOffset, - shouldGetTotalNumResults: true, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - totalNumResults: testData.total.formCount, - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: expect.arrayContaining(testData.total.expectedFormInfo), }) }) - describe('when query.shouldGetTotalNumResults is false', () => { - it('should return only list of form info', async () => { - // Act - const actualResults = await getExampleFormsUsingSubs({ - pageNo: 0, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - // Return should be all forms - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: expect.arrayContaining(testData.total.expectedFormInfo), - }) + it('should return empty list when offset is more than number of documents', async () => { + // Arrange + const overOffset = + (await FormStatsModel.estimatedDocumentCount()) / PAGE_SIZE + 1 + // Act + const actualResults = await ExamplesService.getExampleForms({ + pageNo: overOffset, + shouldGetTotalNumResults: false, }) - it('should return empty list when offset is more than number of documents', async () => { - // Arrange - const overOffset = - (await FormStatsModel.estimatedDocumentCount()) / PAGE_SIZE + 1 - // Act - const actualResults = await getExampleFormsUsingSubs({ - pageNo: overOffset, - shouldGetTotalNumResults: false, - }) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: [], - }) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + forms: [], }) }) }) @@ -325,71 +171,33 @@ describe('examples.service', () => { }) describe('getSingleExampleForm', () => { - describe('with RetrievalType.Stats', () => { - const getSingleFormUsingSubs = ExamplesService.getSingleExampleForm( - RetrievalType.Submissions, - ) - - it('should return form info of given formId when form exists in the database', async () => { - // Arrange - const expectedFormInfo = testData.first.expectedFormInfo[0] - - // Act - const actualResults = await getSingleFormUsingSubs(expectedFormInfo._id) - - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - form: expectedFormInfo, - }) - }) + it('should return form info of given formId when form exists in the database', async () => { + // Arrange + const expectedFormInfo = testData.first.expectedFormInfo[0] - it('should return ResultsNotFoundError when form does not exist in the database', async () => { - // Act - const actualResults = await getSingleFormUsingSubs( - String(new ObjectId()), - ) + // Act + const actualResults = await ExamplesService.getSingleExampleForm( + expectedFormInfo._id, + ) - // Assert - expect(actualResults.isErr()).toEqual(true) - expect(actualResults._unsafeUnwrapErr()).toBeInstanceOf( - ResultsNotFoundError, - ) + // Assert + expect(actualResults.isOk()).toEqual(true) + expect(actualResults._unsafeUnwrap()).toEqual({ + form: expectedFormInfo, }) }) - describe('with RetrievalType.Submissions', () => { - const getSingleFormUsingStats = ExamplesService.getSingleExampleForm( - RetrievalType.Stats, + it('should return ResultsNotFoundError when form does not exist in the database', async () => { + // Act + const actualResults = await ExamplesService.getSingleExampleForm( + String(new ObjectId()), ) - it('should return form info of given formId when form exists in the database', async () => { - // Arrange - const expectedFormInfo = testData.second.expectedFormInfo[1] - - // Act - const actualResults = await getSingleFormUsingStats( - expectedFormInfo._id, - ) - // Assert - expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - form: expectedFormInfo, - }) - }) - - it('should return ResultsNotFoundError when form does not exist in the database', async () => { - // Act - const actualResults = await getSingleFormUsingStats( - String(new ObjectId()), - ) - - // Assert - expect(actualResults.isErr()).toEqual(true) - expect(actualResults._unsafeUnwrapErr()).toBeInstanceOf( - ResultsNotFoundError, - ) - }) + // Assert + expect(actualResults.isErr()).toEqual(true) + expect(actualResults._unsafeUnwrapErr()).toBeInstanceOf( + ResultsNotFoundError, + ) }) }) }) diff --git a/src/app/modules/examples/examples.controller.ts b/src/app/modules/examples/examples.controller.ts index 6a54eaa96b..cc86860784 100644 --- a/src/app/modules/examples/examples.controller.ts +++ b/src/app/modules/examples/examples.controller.ts @@ -10,7 +10,7 @@ import { createLoggerWithLabel } from '../../config/logger' import { createReqMeta } from '../../utils/request' import { ControllerHandler } from '../core/core.types' -import { ExamplesFactory } from './examples.factory' +import * as ExamplesService from './examples.service' import { mapRouteError } from './examples.utils' const logger = createLoggerWithLabel(module) @@ -29,7 +29,7 @@ export const handleGetExamples: ControllerHandler< unknown, ExampleFormsQueryDto > = (req, res) => { - return ExamplesFactory.getExampleForms(req.query) + return ExamplesService.getExampleForms(req.query) .map((result) => res.status(StatusCodes.OK).json(result)) .mapErr((error) => { logger.error({ @@ -61,7 +61,7 @@ export const handleGetExampleByFormId: ControllerHandler< > = (req, res) => { const { formId } = req.params - return ExamplesFactory.getSingleExampleForm(formId) + return ExamplesService.getSingleExampleForm(formId) .map((result) => res.status(StatusCodes.OK).json(result)) .mapErr((error) => { logger.error({ diff --git a/src/app/modules/examples/examples.factory.ts b/src/app/modules/examples/examples.factory.ts deleted file mode 100644 index 44ea0cfa3b..0000000000 --- a/src/app/modules/examples/examples.factory.ts +++ /dev/null @@ -1,30 +0,0 @@ -import FeatureManager, { - FeatureNames, - RegisteredFeature, -} from '../../config/feature-manager' - -import * as ExamplesService from './examples.service' -import { RetrievalType } from './examples.types' - -interface IExamplesFactory { - getExampleForms: ReturnType - getSingleExampleForm: ReturnType -} - -const aggregateFeature = FeatureManager.get(FeatureNames.AggregateStats) - -// Exported for testing. -export const createExamplesFactory = ({ - isEnabled, -}: RegisteredFeature): IExamplesFactory => { - // Set retrieval type to use statistics collection if feature is enabled. - const retrievalType = isEnabled - ? RetrievalType.Stats - : RetrievalType.Submissions - return { - getExampleForms: ExamplesService.getExampleForms(retrievalType), - getSingleExampleForm: ExamplesService.getSingleExampleForm(retrievalType), - } -} - -export const ExamplesFactory = createExamplesFactory(aggregateFeature) diff --git a/src/app/modules/examples/examples.queries.ts b/src/app/modules/examples/examples.queries.ts index 7d654368ad..4d4e46ca4f 100644 --- a/src/app/modules/examples/examples.queries.ts +++ b/src/app/modules/examples/examples.queries.ts @@ -114,8 +114,7 @@ export const filterByAgencyId = ( /** * Precondition: A group stage that produced a count field must be executed - * beforehand, which can be done with groupSubmissionsByFormId or - * lookupSubmissionInfo. + * beforehand, which can be done with lookupSubmissionInfo. * Aggregation step to filter forms with less than the minimum number of * submissions for the examples page. @@ -162,18 +161,6 @@ export const sortByLastSubmitted: Record[] = [ }, ] -/** - * Precondition: `created` field must have already been retrieved from the - * submissions collection via searchSubmissionsForForm. - * - * Aggregation step to sort forms by the creation date. - */ -export const sortByCreated = [ - { - $sort: { created: 1 }, - }, -] - /** * Precondition: `_id` field corresponding to the forms' ids must be retrieved * beforehand, which can be done using groupSubmissionsByFormId or @@ -250,63 +237,6 @@ export const lookupFormStatisticsInfo: Record[] = [ }, ] -/** - * Precondition: `_id` field corresponding to forms' ids must be retrieved - * beforehand, which can be done using groupSubmissionsByFormId or - * searchFormsForText. - * - * Aggregation step to retrieve submissionInfo by looking up, sorting and - * grouping submissions with form ids specified. - * - */ -export const lookupSubmissionInfo = [ - { - $lookup: { - from: 'submissions', - localField: '_id', - foreignField: 'form', - as: 'submissionInfo', - }, - }, - // Unwind results in multiple copies of each form, where each copy has its own submissionInfo - { - $unwind: '$submissionInfo', - }, - { - $sort: { 'submissionInfo.created': 1 }, - }, - // Retrieve only the necessary information from the submissionInfo - { - $group: { - _id: '$_id', - count: { $sum: 1 }, - formInfo: { $first: '$formInfo' }, - agencyInfo: { $first: '$agencyInfo' }, - lastSubmission: { $last: '$submissionInfo.created' }, - textScore: { $first: { $meta: 'textScore' } }, // Used to sort by relevance - }, - }, -] - -/** - * Precondition: `_id` field corresponding to forms' ids must be retrieved - * beforehand. - * - * !Note: Can only used on pipelines working with the Submissions collection. - * - * Aggregation step to group submissions by form id, count the number of - * submissions, and get the last submission date. - */ -export const groupSubmissionsByFormId = [ - { - $group: { - _id: '$form', - count: { $sum: 1 }, - lastSubmission: { $last: '$created' }, - }, - }, -] - /** * Precondition: `_id` field corresponding to forms' ids must be retrieved * beforehand. @@ -420,19 +350,3 @@ export const searchSubmissionsForForm = ( }, }, ] - -/** - * Precondition: `formFeedbackInfo` must have been retrieved in a previous step, - * which can be done using lookupFormFeedback. - * - * Aggregation step to add the average feedback field. - */ -export const addAvgFeedback = [ - { - $addFields: { - avgFeedback: { - $avg: '$formFeedbackInfo.rating', - }, - }, - }, -] diff --git a/src/app/modules/examples/examples.service.ts b/src/app/modules/examples/examples.service.ts index d6c7c907c2..ddd3ef6281 100644 --- a/src/app/modules/examples/examples.service.ts +++ b/src/app/modules/examples/examples.service.ts @@ -6,28 +6,23 @@ import { Except, Merge } from 'type-fest' import { createLoggerWithLabel } from '../../config/logger' import getFormModel from '../../models/form.server.model' import getFormStatisticsTotalModel from '../../models/form_statistics_total.server.model' -import getSubmissionModel from '../../models/submission.server.model' import { DatabaseError } from '../core/core.errors' import { MIN_SUB_COUNT, PAGE_SIZE } from './examples.constants' import { ResultsNotFoundError } from './examples.errors' import { - groupSubmissionsByFormId, lookupFormStatisticsInfo, - lookupSubmissionInfo, projectSubmissionInfo, selectAndProjectCardInfo, } from './examples.queries' import { ExamplesQueryParams, FormInfo, - QueryData, QueryDataMap, QueryExecResult, QueryExecResultWithTotal, QueryPageResult, QueryPageResultWithTotal, - RetrievalType, RetrieveSubmissionsExecResult, SingleFormInfoQueryResult, SingleFormResult, @@ -37,35 +32,14 @@ import { createGeneralQueryPipeline, createSearchQueryPipeline, createSingleSearchStatsPipeline, - createSingleSearchSubmissionPipeline, formatToRelativeString, } from './examples.utils' const FormModel = getFormModel(mongoose) const FormStatisticsModel = getFormStatisticsTotalModel(mongoose) -const SubmissionModel = getSubmissionModel(mongoose) const logger = createLoggerWithLabel(module) -/** - * Maps retrieval type to the middlewares and query model used for general - * queries to use when creating the aggregation pipeline - */ -const RETRIEVAL_TO_QUERY_DATA_MAP: QueryData = { - [RetrievalType.Stats]: { - generalQueryModel: FormStatisticsModel, - lookUpMiddleware: lookupFormStatisticsInfo, - groupByMiddleware: projectSubmissionInfo, - singleSearchPipeline: createSingleSearchStatsPipeline, - }, - [RetrievalType.Submissions]: { - generalQueryModel: SubmissionModel, - lookUpMiddleware: lookupSubmissionInfo, - groupByMiddleware: groupSubmissionsByFormId, - singleSearchPipeline: createSingleSearchSubmissionPipeline, - }, -} - /** * Creates and returns the query builder to execute some example fetch query. */ @@ -239,28 +213,23 @@ const getFormInfo = ( * @returns ok(list of retrieved example forms) if `shouldGetTotalNumResults` is not of string `"true"` * @returns err(DatabaseError) if any errors occurs whilst running the pipeline on the database */ -export const getExampleForms = - (type: RetrievalType) => - ( - query: ExamplesQueryParams, - ): ResultAsync => { - const { lookUpMiddleware, groupByMiddleware, generalQueryModel } = - RETRIEVAL_TO_QUERY_DATA_MAP[type] - - const queryBuilder = getExamplesQueryBuilder({ - query, - lookUpMiddleware, - groupByMiddleware, - generalQueryModel, - }) +export const getExampleForms = ( + query: ExamplesQueryParams, +): ResultAsync => { + const queryBuilder = getExamplesQueryBuilder({ + query, + lookUpMiddleware: lookupFormStatisticsInfo, + groupByMiddleware: projectSubmissionInfo, + generalQueryModel: FormStatisticsModel, + }) - const { pageNo, shouldGetTotalNumResults } = query - const offset = pageNo * PAGE_SIZE || 0 + const { pageNo, shouldGetTotalNumResults } = query + const offset = pageNo * PAGE_SIZE || 0 - return shouldGetTotalNumResults - ? execExamplesQueryWithTotal(queryBuilder, offset) - : execExamplesQuery(queryBuilder, offset) - } + return shouldGetTotalNumResults + ? execExamplesQueryWithTotal(queryBuilder, offset) + : execExamplesQuery(queryBuilder, offset) +} /** * Retrieves a single form for examples from either the FormStatisticsTotal @@ -272,63 +241,57 @@ export const getExampleForms = * @returns err(DatabaseError) if any errors occurs whilst running the pipeline on the database * @returns err(ResultsNotFoundError) if form info cannot be retrieved with the given form id */ -export const getSingleExampleForm = - (type: RetrievalType) => - ( - formId: string, - ): ResultAsync => { - const { singleSearchPipeline, generalQueryModel } = - RETRIEVAL_TO_QUERY_DATA_MAP[type] - - return ( - // Step 1: Retrieve base form info to augment. - getFormInfo(formId) - // Step 2a: Execute aggregate query with relevant single search pipeline. - .andThen((formInfo) => - ResultAsync.fromPromise( - generalQueryModel - .aggregate(singleSearchPipeline(formId)) - .read('secondary') - .exec() as Promise, - (error) => { - logger.error({ - message: 'Failed to retrieve a single example form', - meta: { - action: 'getSingleExampleForm', - }, - error, - }) - - return new DatabaseError() - }, - // Step 2b: Augment the initial base form info with the retrieved - // statistics from the aggregate pipeline. - ).map((queryResult) => { - // Process result depending on whether search pipeline returned - // results. - // If the statistics cannot be found, add default "null" fields. - if (!queryResult || queryResult.length === 0) { - const emptyStatsExampleInfo: FormInfo = { - ...formInfo, - count: 0, - lastSubmission: null, - timeText: '-', - avgFeedback: null, - } - return { form: emptyStatsExampleInfo } - } +export const getSingleExampleForm = ( + formId: string, +): ResultAsync => { + return ( + // Step 1: Retrieve base form info to augment. + getFormInfo(formId) + // Step 2a: Execute aggregate query with relevant single search pipeline. + .andThen((formInfo) => + ResultAsync.fromPromise( + FormStatisticsModel.aggregate(createSingleSearchStatsPipeline(formId)) + .read('secondary') + .exec() as Promise, + (error) => { + logger.error({ + message: 'Failed to retrieve a single example form', + meta: { + action: 'getSingleExampleForm', + }, + error, + }) - // Statistics can be found. - const [statistics] = queryResult - const processedExampleInfo: FormInfo = { + return new DatabaseError() + }, + // Step 2b: Augment the initial base form info with the retrieved + // statistics from the aggregate pipeline. + ).map((queryResult) => { + // Process result depending on whether search pipeline returned + // results. + // If the statistics cannot be found, add default "null" fields. + if (!queryResult || queryResult.length === 0) { + const emptyStatsExampleInfo: FormInfo = { ...formInfo, - count: statistics.count, - lastSubmission: statistics.lastSubmission, - avgFeedback: statistics.avgFeedback, - timeText: formatToRelativeString(statistics.lastSubmission), + count: 0, + lastSubmission: null, + timeText: '-', + avgFeedback: null, } - return { form: processedExampleInfo } - }), - ) - ) - } + return { form: emptyStatsExampleInfo } + } + + // Statistics can be found. + const [statistics] = queryResult + const processedExampleInfo: FormInfo = { + ...formInfo, + count: statistics.count, + lastSubmission: statistics.lastSubmission, + avgFeedback: statistics.avgFeedback, + timeText: formatToRelativeString(statistics.lastSubmission), + } + return { form: processedExampleInfo } + }), + ) + ) +} diff --git a/src/app/modules/examples/examples.types.ts b/src/app/modules/examples/examples.types.ts index 4293fe0d44..132d406eb9 100644 --- a/src/app/modules/examples/examples.types.ts +++ b/src/app/modules/examples/examples.types.ts @@ -3,26 +3,16 @@ import { IForm, IFormFeedbackSchema, IFormStatisticsTotalModel, - ISubmissionModel, StartPage, } from 'src/types' -export enum RetrievalType { - Stats = 'statistics', - Submissions = 'submissions', -} - export type QueryDataMap = { - generalQueryModel: IFormStatisticsTotalModel | ISubmissionModel + generalQueryModel: IFormStatisticsTotalModel lookUpMiddleware: Record[] groupByMiddleware: Record[] singleSearchPipeline: (formId: string) => Record[] } -export type QueryData = { - [k in RetrievalType]: QueryDataMap -} - export type QueryExecResult = { _id: string count: number diff --git a/src/app/modules/examples/examples.utils.ts b/src/app/modules/examples/examples.utils.ts index 5024e043fb..6a8fe883db 100644 --- a/src/app/modules/examples/examples.utils.ts +++ b/src/app/modules/examples/examples.utils.ts @@ -7,11 +7,9 @@ import { DatabaseError } from '../core/core.errors' import { ResultsNotFoundError } from './examples.errors' import { - addAvgFeedback, filterByAgencyId, filterBySubmissionCount, filterInactiveAndUnlistedForms, - groupSubmissionsByFormId, lookupAgencyInfo, lookupFormFeedback, lookupFormInfo, @@ -21,7 +19,6 @@ import { searchFormsById, searchFormsWithText, searchSubmissionsForForm, - sortByCreated, sortByLastSubmitted, sortByRelevance, } from './examples.queries' @@ -169,35 +166,6 @@ export const createFormIdInfoPipeline = ( ) } -/** - * Creates a query pipeline that can be used to retrieve a single example form - * for the /examples page using the submission collection. - * - * This pipeline will return the average feedback for the form id referenced to - * be shown in the example form. - * @param formId. The id of the form to retrieve data for - */ -export const createSingleSearchSubmissionPipeline = ( - formId: string, -): Record[] => { - // Retrieve all submissions with the specified formId. - // This pipeline using the submission collection, and `form` is the foreign - // key of the form collection in that collection. - return searchSubmissionsForForm('form', formId).concat( - // Sort forms by the creation date. - sortByCreated, - // Group submissions by form id, count the number of submissions, and get - // the last submission date. - groupSubmissionsByFormId, - // Sort all submissions by their last submission date. - sortByLastSubmitted, - // Retrieve form feedbacks for the submissions. - lookupFormFeedback, - // Calculate and add the average feedback. - addAvgFeedback, - ) -} - /** * Creates a query pipeline that can be used to retrieve a single example form * for the /examples page using the formStatisticsTotal collection. diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index e719669a1e..05a20e680d 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -22,6 +22,7 @@ import { import * as EmailSubmissionService from 'src/app/modules/submission/email-submission/email-submission.service' import * as EmailSubmissionUtil from 'src/app/modules/submission/email-submission/email-submission.util' import * as EncryptSubmissionService from 'src/app/modules/submission/encrypt-submission/encrypt-submission.service' +import IncomingEncryptSubmission from 'src/app/modules/submission/encrypt-submission/IncomingEncryptSubmission.class' import { ConflictError, InvalidEncodingError, @@ -31,13 +32,13 @@ import { ValidateFieldError, } from 'src/app/modules/submission/submission.errors' import * as SubmissionService from 'src/app/modules/submission/submission.service' +import * as SubmissionUtils from 'src/app/modules/submission/submission.utils' import { MissingUserError } from 'src/app/modules/user/user.errors' import { MailGenerationError, MailSendError, } from 'src/app/services/mail/mail.errors' import MailService from 'src/app/services/mail/mail.service' -import * as EncryptionUtils from 'src/app/utils/encryption' import { EditFieldActions } from 'src/shared/constants' import { AuthType, @@ -109,9 +110,21 @@ const MockEncryptSubmissionService = mocked(EncryptSubmissionService) jest.mock( 'src/app/modules/submission/email-submission/email-submission.service', ) +jest.mock( + 'src/app/modules/submission/encrypt-submission/IncomingEncryptSubmission.class', +) +const MockIncomingEncryptSubmission = mocked(IncomingEncryptSubmission) +jest.mock( + 'src/app/modules/submission/encrypt-submission/encrypt-submission.utils', + () => ({ + ...jest.requireActual( + 'src/app/modules/submission/encrypt-submission/encrypt-submission.utils', + ), + }), +) const MockEmailSubmissionService = mocked(EmailSubmissionService) -jest.mock('src/app/utils/encryption') -const MockEncryptionUtils = mocked(EncryptionUtils) +jest.mock('src/app/modules/submission/submission.utils') +const MockSubmissionUtils = mocked(SubmissionUtils) jest.mock('../admin-form.service') const MockAdminFormService = mocked(AdminFormService) jest.mock('../../form.service') @@ -5123,6 +5136,7 @@ describe('admin-form.controller', () => { MockEmailSubmissionService.createEmailSubmissionWithoutSave.mockReturnValue( MOCK_SUBMISSION, ) + MockSubmissionUtils.extractEmailConfirmationData.mockReturnValue([]) MockEmailSubmissionService.extractEmailAnswers.mockReturnValue([ MOCK_RESPONSES[0].answer, ]) @@ -5196,10 +5210,10 @@ describe('admin-form.controller', () => { expect(MockSubmissionService.sendEmailConfirmations).toHaveBeenCalledWith( { form: MOCK_FORM, - parsedResponses: MOCK_PARSED_RESPONSES, submission: MOCK_SUBMISSION, attachments: [], - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, + recipientData: [], }, ) expect(mockRes.json).toHaveBeenCalledWith({ @@ -6034,10 +6048,10 @@ describe('admin-form.controller', () => { expect(MockSubmissionService.sendEmailConfirmations).toHaveBeenCalledWith( { form: MOCK_FORM, - parsedResponses: MOCK_PARSED_RESPONSES, submission: MOCK_SUBMISSION, attachments: [], - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, + recipientData: [], }, ) expect(mockRes.json).toHaveBeenCalledWith({ @@ -6051,9 +6065,6 @@ describe('admin-form.controller', () => { const MOCK_RESPONSES = [ generateUnprocessedSingleAnswerResponse(BasicField.Email), ] - const MOCK_PARSED_RESPONSES = [ - generateNewSingleAnswerResponse(BasicField.Email), - ] const MOCK_ENCRYPTED_CONTENT = 'mockEncryptedContent' const MOCK_VERSION = 1 const MOCK_SUBMISSION_BODY: EncryptSubmissionDto = { @@ -6086,6 +6097,7 @@ describe('admin-form.controller', () => { _id: MOCK_SUBMISSION_ID, created: new Date(), } as IEncryptedSubmissionSchema + const mockIncomingEncryptSubmissionInit = jest.fn() beforeEach(() => { MockUserService.getPopulatedUserById.mockReturnValue(okAsync(MOCK_USER)) @@ -6095,9 +6107,16 @@ describe('admin-form.controller', () => { MockEncryptSubmissionService.checkFormIsEncryptMode.mockReturnValue( ok(MOCK_FORM), ) - MockEncryptionUtils.checkIsEncryptedEncoding.mockReturnValue(ok(true)) - MockSubmissionService.getProcessedResponses.mockReturnValue( - ok(MOCK_PARSED_RESPONSES), + MockIncomingEncryptSubmission.init = mockIncomingEncryptSubmissionInit + mockIncomingEncryptSubmissionInit.mockReturnValue( + ok({ + responses: MOCK_RESPONSES, + form: MOCK_FORM, + encryptedContent: MOCK_ENCRYPTED_CONTENT, + } as IncomingEncryptSubmission), + ) + MockSubmissionUtils.extractEmailConfirmationDataFromIncomingSubmission.mockReturnValue( + [], ) MockEncryptSubmissionService.createEncryptSubmissionWithoutSave.mockReturnValue( MOCK_SUBMISSION, @@ -6137,12 +6156,10 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect(MockEncryptionUtils.checkIsEncryptedEncoding).toHaveBeenCalledWith( - MOCK_ENCRYPTED_CONTENT, - ) - expect(MockSubmissionService.getProcessedResponses).toHaveBeenCalledWith( + expect(IncomingEncryptSubmission.init).toHaveBeenCalledWith( MOCK_FORM, MOCK_RESPONSES, + MOCK_ENCRYPTED_CONTENT, ) expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, @@ -6155,8 +6172,8 @@ describe('admin-form.controller', () => { expect(MockSubmissionService.sendEmailConfirmations).toHaveBeenCalledWith( { form: MOCK_FORM, - parsedResponses: MOCK_PARSED_RESPONSES, submission: MOCK_SUBMISSION, + recipientData: [], }, ) expect(mockRes.json).toHaveBeenCalledWith({ @@ -6197,10 +6214,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6245,10 +6259,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6297,10 +6308,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6349,10 +6357,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6401,10 +6406,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6453,10 +6455,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).not.toHaveBeenCalled() - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6505,10 +6504,7 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect( - MockEncryptionUtils.checkIsEncryptedEncoding, - ).not.toHaveBeenCalled() - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() + expect(IncomingEncryptSubmission.init).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6522,7 +6518,7 @@ describe('admin-form.controller', () => { }) it('should return 400 when encrypted content encoding is invalid', async () => { - MockEncryptionUtils.checkIsEncryptedEncoding.mockReturnValueOnce( + mockIncomingEncryptSubmissionInit.mockReturnValueOnce( err(new InvalidEncodingError()), ) const mockReq = expressHandler.mockRequest({ @@ -6557,10 +6553,11 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect(MockEncryptionUtils.checkIsEncryptedEncoding).toHaveBeenCalledWith( + expect(IncomingEncryptSubmission.init).toHaveBeenCalledWith( + MOCK_FORM, + MOCK_RESPONSES, MOCK_ENCRYPTED_CONTENT, ) - expect(MockSubmissionService.getProcessedResponses).not.toHaveBeenCalled() expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, ).not.toHaveBeenCalled() @@ -6574,7 +6571,7 @@ describe('admin-form.controller', () => { }) it('should return 400 when responses cannot be processed', async () => { - MockSubmissionService.getProcessedResponses.mockReturnValueOnce( + mockIncomingEncryptSubmissionInit.mockReturnValueOnce( err(new ProcessingError()), ) const mockReq = expressHandler.mockRequest({ @@ -6609,12 +6606,10 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect(MockEncryptionUtils.checkIsEncryptedEncoding).toHaveBeenCalledWith( - MOCK_ENCRYPTED_CONTENT, - ) - expect(MockSubmissionService.getProcessedResponses).toHaveBeenCalledWith( + expect(IncomingEncryptSubmission.init).toHaveBeenCalledWith( MOCK_FORM, MOCK_RESPONSES, + MOCK_ENCRYPTED_CONTENT, ) expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, @@ -6629,7 +6624,7 @@ describe('admin-form.controller', () => { }) it('should return 409 when form fields submitted are not updated', async () => { - MockSubmissionService.getProcessedResponses.mockReturnValueOnce( + mockIncomingEncryptSubmissionInit.mockReturnValueOnce( err(new ConflictError('')), ) const mockReq = expressHandler.mockRequest({ @@ -6664,12 +6659,10 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect(MockEncryptionUtils.checkIsEncryptedEncoding).toHaveBeenCalledWith( - MOCK_ENCRYPTED_CONTENT, - ) - expect(MockSubmissionService.getProcessedResponses).toHaveBeenCalledWith( + expect(IncomingEncryptSubmission.init).toHaveBeenCalledWith( MOCK_FORM, MOCK_RESPONSES, + MOCK_ENCRYPTED_CONTENT, ) expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, @@ -6684,7 +6677,7 @@ describe('admin-form.controller', () => { }) it('should return 400 when responses cannot be validated', async () => { - MockSubmissionService.getProcessedResponses.mockReturnValueOnce( + mockIncomingEncryptSubmissionInit.mockReturnValueOnce( err(new ValidateFieldError()), ) const mockReq = expressHandler.mockRequest({ @@ -6719,12 +6712,10 @@ describe('admin-form.controller', () => { expect( MockEncryptSubmissionService.checkFormIsEncryptMode, ).toHaveBeenCalledWith(MOCK_FORM) - expect(MockEncryptionUtils.checkIsEncryptedEncoding).toHaveBeenCalledWith( - MOCK_ENCRYPTED_CONTENT, - ) - expect(MockSubmissionService.getProcessedResponses).toHaveBeenCalledWith( + expect(IncomingEncryptSubmission.init).toHaveBeenCalledWith( MOCK_FORM, MOCK_RESPONSES, + MOCK_ENCRYPTED_CONTENT, ) expect( MockEncryptSubmissionService.createEncryptSubmissionWithoutSave, diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts index 7256c76e3f..07fea92b2c 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts @@ -3609,7 +3609,7 @@ describe('admin-form.routes', () => { encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', } - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, ...expectedSubmissionParams, }) @@ -3629,6 +3629,7 @@ describe('admin-form.routes', () => { refNo: String(submission._id), submissionTime: expect.any(String), verified: expectedSubmissionParams.verifiedContent, + version: submission.version, }) }) @@ -3642,7 +3643,7 @@ describe('admin-form.routes', () => { ['fieldId2', 'some.other.attachment.url'], ]), } - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, ...expectedSubmissionParams, }) @@ -3669,6 +3670,7 @@ describe('admin-form.routes', () => { refNo: String(submission._id), submissionTime: expect.any(String), verified: expectedSubmissionParams.verifiedContent, + version: submission.version, }) }) @@ -3819,7 +3821,7 @@ describe('admin-form.routes', () => { jest .spyOn(EncryptSubmissionModel, 'findEncryptedSubmissionById') .mockRejectedValueOnce(new Error('ohno')) - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', @@ -3848,7 +3850,7 @@ describe('admin-form.routes', () => { .spyOn(aws.s3, 'getSignedUrlPromise') .mockRejectedValueOnce(new Error('something went wrong')) - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', @@ -4349,7 +4351,7 @@ describe('admin-form.routes', () => { // Create 11 submissions const submissions = await Promise.all( times(11, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -4384,7 +4386,7 @@ describe('admin-form.routes', () => { it('should return 200 with empty results if query.page does not have metadata', async () => { // Arrange // Create single submission - await createSubmission({ + await createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content`, verifiedContent: `any verified content`, @@ -4412,7 +4414,7 @@ describe('admin-form.routes', () => { // Create 3 submissions const submissions = await Promise.all( times(3, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -4603,7 +4605,7 @@ describe('admin-form.routes', () => { // Arrange const submissions = await Promise.all( times(11, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -4639,6 +4641,7 @@ describe('admin-form.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .sort((a, b) => String(a._id).localeCompare(String(b._id))) @@ -4659,7 +4662,7 @@ describe('admin-form.routes', () => { // Arrange const submissions = await Promise.all( times(5, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -4695,6 +4698,7 @@ describe('admin-form.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .sort((a, b) => String(a._id).localeCompare(String(b._id))) @@ -4723,7 +4727,7 @@ describe('admin-form.routes', () => { // Arrange const submissions = await Promise.all( times(5, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -4774,6 +4778,7 @@ describe('admin-form.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .filter((s) => expectedSubmissionIds.includes(s._id)) @@ -5385,7 +5390,7 @@ describe('admin-form.routes', () => { }) // Helper utils -const createSubmission = ({ +const createEncryptSubmission = ({ form, encryptedContent, verifiedContent, @@ -5398,7 +5403,7 @@ const createSubmission = ({ verifiedContent?: string created?: Date }) => { - return SubmissionModel.create({ + return EncryptSubmissionModel.create({ submissionType: SubmissionType.Encrypt, form: form._id, authType: form.authType, diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index 418f540553..c6ad18fb8c 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -40,7 +40,6 @@ import { } from '../../../../types/api' import { createLoggerWithLabel } from '../../../config/logger' import MailService from '../../../services/mail/mail.service' -import { checkIsEncryptedEncoding } from '../../../utils/encryption' import { createReqMeta } from '../../../utils/request' import * as AuthService from '../../auth/auth.service' import { @@ -65,7 +64,12 @@ import { import * as EncryptSubmissionMiddleware from '../../submission/encrypt-submission/encrypt-submission.middleware' import * as EncryptSubmissionService from '../../submission/encrypt-submission/encrypt-submission.service' import { mapRouteError as mapEncryptSubmissionError } from '../../submission/encrypt-submission/encrypt-submission.utils' +import IncomingEncryptSubmission from '../../submission/encrypt-submission/IncomingEncryptSubmission.class' import * as SubmissionService from '../../submission/submission.service' +import { + extractEmailConfirmationData, + extractEmailConfirmationDataFromIncomingSubmission, +} from '../../submission/submission.utils' import * as UserService from '../../user/user.service' import { PrivateFormError } from '../form.errors' import * as FormService from '../form.service' @@ -1409,23 +1413,22 @@ export const submitEncryptPreview: ControllerHandler< }), ) .andThen((form) => - checkIsEncryptedEncoding(encryptedContent) - .andThen(() => SubmissionService.getProcessedResponses(form, responses)) - .map((parsedResponses) => ({ parsedResponses, form })) + IncomingEncryptSubmission.init(form, responses, encryptedContent) + .map((incomingSubmission) => ({ incomingSubmission, form })) .mapErr((error) => { logger.error({ - message: 'Error while parsing responses for preview submission', + message: 'Error while processing incoming preview submission.', meta: logMeta, error, }) return error }), ) - .map(({ parsedResponses, form }) => { + .map(({ incomingSubmission, form }) => { const submission = EncryptSubmissionService.createEncryptSubmissionWithoutSave({ form, - encryptedContent, + encryptedContent: incomingSubmission.encryptedContent, // Don't bother encrypting and signing mock variables for previews verifiedContent: '', version, @@ -1433,8 +1436,11 @@ export const submitEncryptPreview: ControllerHandler< void SubmissionService.sendEmailConfirmations({ form, - parsedResponses, submission, + recipientData: + extractEmailConfirmationDataFromIncomingSubmission( + incomingSubmission, + ), }) // Return the reply early to the submitter @@ -1455,7 +1461,7 @@ export const handleEncryptPreviewSubmission = [ ] as ControllerHandler[] /** - * Handler for POST /v2/submissions/encrypt/preview/:formId. + * Handler for POST /v2/submissions/email/preview/:formId. * @security session * * @returns 200 with a mock submission ID @@ -1575,10 +1581,13 @@ export const submitEmailPreview: ControllerHandler< // this fails void SubmissionService.sendEmailConfirmations({ form, - parsedResponses, submission, attachments, - autoReplyData: emailData.autoReplyData, + responsesData: emailData.autoReplyData, + recipientData: extractEmailConfirmationData( + parsedResponses, + form.form_fields, + ), }).mapErr((error) => { logger.error({ message: 'Error while sending email confirmations', @@ -2261,6 +2270,8 @@ export const handleUpdateCollaborators = [ .email() .message('Please enter a valid email'), write: Joi.bool().optional(), + // TODO(#2177): Deprecated field in very old documents, remove when migration happens. + read: Joi.bool().optional(), _id: Joi.string().optional(), }), ), diff --git a/src/app/modules/form/form.service.ts b/src/app/modules/form/form.service.ts index 916d7d83ad..cebbd598cc 100644 --- a/src/app/modules/form/form.service.ts +++ b/src/app/modules/form/form.service.ts @@ -16,7 +16,7 @@ import getFormModel, { getEncryptedFormModel, } from '../../models/form.server.model' import getSubmissionModel from '../../models/submission.server.model' -import { IntranetFactory } from '../../services/intranet/intranet.factory' +import { IntranetService } from '../../services/intranet/intranet.service' import { getMongoErrorMessage, transformMongoError, @@ -285,27 +285,21 @@ export const checkIsIntranetFormAccess = ( ip: string, form: IPopulatedForm, ): boolean => { - return ( - IntranetFactory.isIntranetIp(ip) - .andThen((isIntranetUser) => { - // Warn if form is being accessed from within intranet - // and the form has authentication set - if ( - isIntranetUser && - [AuthType.SP, AuthType.CP, AuthType.MyInfo].includes(form.authType) - ) { - logger.warn({ - message: - 'Attempting to access SingPass, CorpPass or MyInfo form from intranet', - meta: { - action: 'checkIsIntranetFormAccess', - formId: form._id, - }, - }) - } - return ok(isIntranetUser) - }) - // This is required becausing the factory can throw missing feature error on initialization - .unwrapOr(false) - ) + const isIntranetUser = IntranetService.isIntranetIp(ip) + // Warn if form is being accessed from within intranet + // and the form has authentication set + if ( + isIntranetUser && + [AuthType.SP, AuthType.CP, AuthType.MyInfo].includes(form.authType) + ) { + logger.warn({ + message: + 'Attempting to access SingPass, CorpPass or MyInfo form from intranet', + meta: { + action: 'checkIsIntranetFormAccess', + formId: form._id, + }, + }) + } + return isIntranetUser } diff --git a/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts b/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts deleted file mode 100644 index 37c17b3122..0000000000 --- a/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { StatusCodes } from 'http-status-codes' - -import { FeatureNames, RegisteredFeature } from 'src/app/config/feature-manager' - -import expressHandler from 'tests/unit/backend/helpers/jest-express' - -import { createGoogleAnalyticsFactory } from '../google-analytics.factory' - -describe('google-analytics.factory', () => { - afterEach(() => jest.clearAllMocks()) - const mockReq = expressHandler.mockRequest({ - others: { - app: { - locals: { - GATrackingID: 'abc', - appName: 'xyz', - environment: 'efg', - }, - }, - }, - }) - const mockRes = expressHandler.mockResponse() - - it('should call res correctly if google-analytics feature is disabled', () => { - const MOCK_DISABLED_GA_FEATURE: RegisteredFeature = - { - isEnabled: false, - } - - const GoogleAnalyticsFactory = createGoogleAnalyticsFactory( - MOCK_DISABLED_GA_FEATURE, - ) - - GoogleAnalyticsFactory.addGoogleAnalyticsData(mockReq, mockRes) - - expect(mockRes.type).toHaveBeenCalledWith('text/javascript') - expect(mockRes.sendStatus).toHaveBeenCalledWith(StatusCodes.OK) - expect(mockRes.send).not.toHaveBeenCalled() - }) - - it('should call res correctly if google-analytics feature is enabled', () => { - const MOCK_ENABLED_GA_FEATURE: RegisteredFeature = - { - isEnabled: true, - } - - const GoogleAnalyticsFactory = createGoogleAnalyticsFactory( - MOCK_ENABLED_GA_FEATURE, - ) - - GoogleAnalyticsFactory.addGoogleAnalyticsData(mockReq, mockRes) - - expect(mockRes.send).toHaveBeenCalledWith(expect.stringContaining('gtag')) - expect(mockRes.type).toHaveBeenCalledWith('text/javascript') - expect(mockRes.status).toHaveBeenCalledWith(StatusCodes.OK) - }) -}) diff --git a/src/app/modules/frontend/frontend.controller.ts b/src/app/modules/frontend/frontend.controller.ts index d062e5fa3e..3268e9552d 100644 --- a/src/app/modules/frontend/frontend.controller.ts +++ b/src/app/modules/frontend/frontend.controller.ts @@ -1,7 +1,6 @@ import ejs from 'ejs' import { StatusCodes } from 'http-status-codes' -import featureManager from '../../config/feature-manager' import { createLoggerWithLabel } from '../../config/logger' import { createReqMeta } from '../../utils/request' import { ControllerHandler } from '../core/core.types' @@ -116,15 +115,38 @@ export const generateRedirectUrl: ControllerHandler< } } +// Duplicated here since the feature manager is being deprecated. +// TODO (#2147): delete this. +enum FeatureNames { + Captcha = 'captcha', + GoogleAnalytics = 'google-analytics', + Sentry = 'sentry', + Sms = 'sms', + SpcpMyInfo = 'spcp-myinfo', + VerifiedFields = 'verified-fields', + WebhookVerifiedContent = 'webhook-verified-content', +} + /** * Handler for GET /frontend/features endpoint. * @param _req - Express request object * @param res - Express response object * @returns Current featureManager states + * @deprecated as the feature manager has been deprecated. This endpoint + * now hardcodes the feature states to support old clients. + * TODO (#2147): delete this */ export const showFeaturesStates: ControllerHandler< unknown, - typeof featureManager.states + Record > = (_req, res) => { - return res.json(featureManager.states) + return res.json({ + [FeatureNames.Captcha]: true, + [FeatureNames.Sms]: true, + [FeatureNames.SpcpMyInfo]: true, + [FeatureNames.VerifiedFields]: true, + [FeatureNames.GoogleAnalytics]: true, + [FeatureNames.WebhookVerifiedContent]: true, + [FeatureNames.Sentry]: true, + }) } diff --git a/src/app/modules/frontend/frontend.routes.ts b/src/app/modules/frontend/frontend.routes.ts index 4e823f2967..0fc0bdfddd 100644 --- a/src/app/modules/frontend/frontend.routes.ts +++ b/src/app/modules/frontend/frontend.routes.ts @@ -2,7 +2,6 @@ import { celebrate, Joi, Segments } from 'celebrate' import { Router } from 'express' import * as FrontendServerController from './frontend.controller' -import { GoogleAnalyticsFactory } from './google-analytics.factory' export const FrontendRouter = Router() @@ -13,7 +12,10 @@ export const FrontendRouter = Router() * @return 200 when code generation is successful * @return 400 when code generation fails */ -FrontendRouter.get('/datalayer', GoogleAnalyticsFactory.addGoogleAnalyticsData) +FrontendRouter.get( + '/datalayer', + FrontendServerController.addGoogleAnalyticsData, +) /** * Generate the templated Javascript code with environment variables for the frontend @@ -27,6 +29,8 @@ FrontendRouter.get('/environment', FrontendServerController.addEnvVarData) * Generate a json of current activated features * @route GET /frontend/features * @return json with featureManager.states + * @deprecated + * TODO (#2147): delete this */ FrontendRouter.get('/features', FrontendServerController.showFeaturesStates) diff --git a/src/app/modules/frontend/google-analytics.factory.ts b/src/app/modules/frontend/google-analytics.factory.ts deleted file mode 100644 index 922e7e38a8..0000000000 --- a/src/app/modules/frontend/google-analytics.factory.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { StatusCodes } from 'http-status-codes' - -import FeatureManager, { - FeatureNames, - RegisteredFeature, -} from '../../config/feature-manager' -import { ControllerHandler } from '../core/core.types' - -import * as FrontendServerController from './frontend.controller' - -interface IGoogleAnalyticsFactory { - addGoogleAnalyticsData: ControllerHandler< - unknown, - string | { message: string } - > -} - -const googleAnalyticsFeature = FeatureManager.get(FeatureNames.GoogleAnalytics) - -/** - * Factory function which returns the correct handler - * for /frontend/datalayer endpoint depending on googleAnalyticsFeature.isEnabled - * @param googleAnalyticsFeature - */ -export const createGoogleAnalyticsFactory = ( - googleAnalyticsFeature: RegisteredFeature, -): IGoogleAnalyticsFactory => { - if (!googleAnalyticsFeature.isEnabled) { - return { - addGoogleAnalyticsData: (req, res) => { - res.type('text/javascript').sendStatus(StatusCodes.OK) - }, - } - } - return { - addGoogleAnalyticsData: FrontendServerController.addGoogleAnalyticsData, - } -} - -export const GoogleAnalyticsFactory = createGoogleAnalyticsFactory( - googleAnalyticsFeature, -) diff --git a/src/app/modules/submission/IncomingSubmission.class.ts b/src/app/modules/submission/IncomingSubmission.class.ts new file mode 100644 index 0000000000..9aa549a0ba --- /dev/null +++ b/src/app/modules/submission/IncomingSubmission.class.ts @@ -0,0 +1,192 @@ +import { combineWithAllErrors, err, ok, Result } from 'neverthrow' + +import { + FieldIdSet, + getLogicUnitPreventingSubmit, + getVisibleFieldIds, +} from '../../../shared/util/logic' +import { + FieldResponse, + IFieldSchema, + IFormDocument, + IPopulatedForm, +} from '../../../types' +import { validateField } from '../../utils/field-validation' + +import { ProcessingError, ValidateFieldError } from './submission.errors' +import { + FilteredResponse, + ProcessedFieldResponse, + ValidatedFieldMap, + VerifiableResponseIdSet, + VisibleResponseIdSet, +} from './submission.types' + +export abstract class IncomingSubmission { + private readonly visibleFieldIds: FieldIdSet + private readonly visibleResponseIds: VisibleResponseIdSet + private readonly verifiableResponseIds: VerifiableResponseIdSet + protected constructor( + public readonly responses: FilteredResponse[], + public readonly form: IPopulatedForm, + private fieldMap: ValidatedFieldMap, + ) { + this.visibleFieldIds = getVisibleFieldIds(responses, form) + this.visibleResponseIds = this.getVisibleResponseIds() + this.verifiableResponseIds = this.getVerifiableResponseIds() + } + + /** + * Generate a look-up table that maps an response ID to form field. + * Additionally, guarantees that every response's ID can be found. + * @param form - The form to generate the lookup table for. + * @param responses - List of responses. Used for validation to achieve + * said guarantee. + * @returns neverthrow ok() with look-up table. + * @returns neverthrow err() if form object does not have `form_fields`, + * or if there exists a response whose ID is not a key in the lookup + * table. + * @protected + */ + protected static getFieldMap( + form: IFormDocument, + responses: FieldResponse[], + ): Result { + if (!form.form_fields) { + return err(new ProcessingError('Form fields are undefined')) + } + + const fieldMap = form.form_fields.reduce<{ + [fieldId: string]: IFieldSchema + }>((acc, field) => { + acc[field._id] = field + return acc + }, {}) + + if (!this.isFieldMapValid(fieldMap, responses)) { + return err( + new ProcessingError('Response ID does not match form field IDs'), + ) + } + + return ok(fieldMap) + } + + private static isFieldMapValid( + fieldMap: { + [p: string]: IFieldSchema + }, + responses: FieldResponse[], + ): fieldMap is ValidatedFieldMap { + for (const r of responses) { + const responseId = r._id + const formField = fieldMap[responseId] + if (!formField) { + return false + } + } + return true + } + + /** + * Generates a set of response IDs that are visible. + * @returns visibleResponseIds - Generated set of response IDs. + * @protected + */ + private getVisibleResponseIds(): VisibleResponseIdSet { + return this.responses.reduce((acc, response) => { + const responseId = String(response._id) + if (this.responseVisibilityPredicate(response)) { + acc.add(responseId) + } + return acc + }, new Set()) as VisibleResponseIdSet + } + + /** + * Generates a set of response IDs that are verifiable. A response is + * said to be verifiable if its corresponding form field has the + * isVerifiable property set to `true`. + * @returns verifiableResponseIds - Generated set of response IDs. + * @protected + */ + private getVerifiableResponseIds(): VerifiableResponseIdSet { + return this.responses.reduce((acc, response) => { + const responseId = String(response._id) + const formField = this.fieldMap[responseId] + if (formField.isVerifiable) { + acc.add(responseId) + } + return acc + }, new Set()) as VerifiableResponseIdSet + } + + /** + * Prior to the introduction of IncomingResponse, responses were + * stored in a ProcessedFieldResponse[]. This method helps to + * create a ProcessedFieldResponse object that can be used in + * functions that have not been refactored to take in the new + * IncomingResponse object. + * + * A ProcessedResponse is just a regular FieldResponse with some + * metadata inserted into them. This function takes in several + * data sets that help determine what each metadata field should + * contain. + * @param response + * @returns processedFieldResponse - The ProcessedFieldResponse + * @protected + */ + protected getLegacyProcessedFieldResponse( + response: FieldResponse, + ): ProcessedFieldResponse { + const responseId = String(response._id) + const formField = this.fieldMap[responseId] + return { + ...response, + isVisible: this.visibleResponseIds.has(responseId), + question: formField.getQuestion(), + isUserVerified: this.verifiableResponseIds.has(responseId) + ? true + : undefined, + } + } + + protected validate(): Result { + // Guard against invalid form submissions that should have been prevented by + // logic. + if ( + getLogicUnitPreventingSubmit( + this.responses, + this.form, + this.visibleFieldIds, + ) + ) { + return err(new ProcessingError('Submission prevented by form logic')) + } + + const validationResultList = this.responses.map((response) => { + const responseId = String(response._id) + const formField = this.fieldMap[responseId] + return validateField( + this.form._id, + formField, + this.getLegacyProcessedFieldResponse(response), + ) + }) + + const validationResultCombined = combineWithAllErrors(validationResultList) + if (validationResultCombined.isErr()) { + return err(validationResultCombined.error) + } + + return ok(true) + } + + /** + * Predicate that determines if a response is visible. To be implemented in + * derived class. + * @param response + * @returns boolean + */ + abstract responseVisibilityPredicate(response: FieldResponse): boolean +} diff --git a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts index 4a04c1180b..db1cf95366 100644 --- a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts +++ b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts @@ -43,6 +43,7 @@ import { SendEmailConfirmationError, ValidateFieldError, } from '../../submission.errors' +import { extractEmailConfirmationData } from '../../submission.utils' jest.mock('src/app/services/mail/mail.service') const MockMailService = mocked(MailService, true) @@ -588,12 +589,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) const expectedAutoReplyData = [ @@ -624,13 +629,17 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) expect(MockMailService.sendAutoReplyEmails).not.toHaveBeenCalled() @@ -666,12 +675,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) expect(MockMailService.sendAutoReplyEmails).not.toHaveBeenCalled() @@ -713,12 +726,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) const expectedAutoReplyData = [EXPECTED_AUTOREPLY_DATA_1] @@ -772,12 +789,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: undefined, + responsesData: undefined, }) const expectedAutoReplyData = [ @@ -834,12 +855,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: undefined, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) const expectedAutoReplyData = [ @@ -889,12 +914,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) const expectedAutoReplyData = [ @@ -954,12 +983,16 @@ describe('submission.service', () => { }), }, ] + const recipientData = extractEmailConfirmationData( + responses, + mockForm.form_fields, + ) const result = await SubmissionService.sendEmailConfirmations({ form: mockForm, - parsedResponses: responses, + recipientData, submission: MOCK_SUBMISSION, attachments: MOCK_ATTACHMENTS, - autoReplyData: MOCK_AUTOREPLY_DATA, + responsesData: MOCK_AUTOREPLY_DATA, }) const expectedAutoReplyData = [ diff --git a/src/app/modules/submission/email-submission/email-submission.controller.ts b/src/app/modules/submission/email-submission/email-submission.controller.ts index c6ebd63aa7..a5f99e5271 100644 --- a/src/app/modules/submission/email-submission/email-submission.controller.ts +++ b/src/app/modules/submission/email-submission/email-submission.controller.ts @@ -25,6 +25,7 @@ import { } from '../../spcp/spcp.util' import * as EmailSubmissionMiddleware from '../email-submission/email-submission.middleware' import * as SubmissionService from '../submission.service' +import { extractEmailConfirmationData } from '../submission.utils' import * as EmailSubmissionService from './email-submission.service' import { IPopulatedEmailFormWithResponsesAndHash } from './email-submission.types' @@ -307,10 +308,13 @@ const submitEmailModeForm: ControllerHandler< // Send email confirmations void SubmissionService.sendEmailConfirmations({ form, - parsedResponses, submission, attachments, - autoReplyData: emailData.autoReplyData, + responsesData: emailData.autoReplyData, + recipientData: extractEmailConfirmationData( + parsedResponses, + form.form_fields, + ), }).mapErr((error) => { // NOTE: MyInfo access token is not cleared here. // This is because if the reason for failure is not on the users' end, diff --git a/src/app/modules/submission/encrypt-submission/IncomingEncryptSubmission.class.ts b/src/app/modules/submission/encrypt-submission/IncomingEncryptSubmission.class.ts new file mode 100644 index 0000000000..35381d6ca3 --- /dev/null +++ b/src/app/modules/submission/encrypt-submission/IncomingEncryptSubmission.class.ts @@ -0,0 +1,64 @@ +import { Result } from 'neverthrow' + +import { FieldResponse, IPopulatedEncryptedForm } from '../../../../types' +import { checkIsEncryptedEncoding } from '../../../utils/encryption' +import { IncomingSubmission } from '../IncomingSubmission.class' +import { + ConflictError, + ProcessingError, + ValidateFieldError, +} from '../submission.errors' +import { FilteredResponse, ValidatedFieldMap } from '../submission.types' +import { getFilteredResponses } from '../submission.utils' + +export default class IncomingEncryptSubmission extends IncomingSubmission { + public readonly encryptedContent: string + private constructor({ + responses, + fieldMap, + form, + encryptedContent, + }: { + responses: FilteredResponse[] + fieldMap: ValidatedFieldMap + form: IPopulatedEncryptedForm + encryptedContent: string + }) { + super(responses, form, fieldMap) + this.encryptedContent = encryptedContent + } + + static init( + form: IPopulatedEncryptedForm, + responses: FieldResponse[], + encryptedContent: string, + ): Result< + IncomingEncryptSubmission, + ProcessingError | ConflictError | ValidateFieldError[] + > { + return checkIsEncryptedEncoding(encryptedContent) + .andThen(() => getFilteredResponses(form, responses)) + .andThen((filteredResponses) => + this.getFieldMap(form, filteredResponses).map((fieldMap) => ({ + responses: filteredResponses, + fieldMap, + form, + encryptedContent, + })), + ) + .map((metadata) => new IncomingEncryptSubmission(metadata)) + .andThen((incomingEncryptSubmission) => + incomingEncryptSubmission + .validate() + .map(() => incomingEncryptSubmission), + ) + } + + responseVisibilityPredicate(response: FieldResponse): boolean { + return ( + 'answer' in response && + typeof response.answer === 'string' && + response.answer.trim() !== '' + ) + } +} diff --git a/src/app/modules/submission/encrypt-submission/__tests__/IncomingEncryptSubmission.class.spec.ts b/src/app/modules/submission/encrypt-submission/__tests__/IncomingEncryptSubmission.class.spec.ts new file mode 100644 index 0000000000..d6de771af5 --- /dev/null +++ b/src/app/modules/submission/encrypt-submission/__tests__/IncomingEncryptSubmission.class.spec.ts @@ -0,0 +1,66 @@ +import { ok } from 'neverthrow' +import { mocked } from 'ts-jest/utils' + +import { + generateDefaultField, + generateSingleAnswerResponse, +} from '../../../../../../tests/unit/backend/helpers/generate-form-data' +import { + BasicField, + IPopulatedEncryptedForm, + ResponseMode, +} from '../../../../../types' +import { checkIsEncryptedEncoding } from '../../../../utils/encryption' +import { ConflictError } from '../../submission.errors' +import IncomingEncryptSubmission from '../IncomingEncryptSubmission.class' + +jest.mock('../../../../utils/encryption') +const mockCheckIsEncryptedEncoding = mocked(checkIsEncryptedEncoding) + +describe('IncomingEncryptSubmission', () => { + it('should create an incoming encrypt submission with valid form and responses', () => { + mockCheckIsEncryptedEncoding.mockReturnValueOnce(ok(true)) + const mobileField = generateDefaultField(BasicField.Mobile) + const emailField = generateDefaultField(BasicField.Email) + const mobileResponse = generateSingleAnswerResponse( + mobileField, + '+6587654321', + ) + const emailResponse = generateSingleAnswerResponse( + emailField, + 'test@example.com', + ) + const responses = [mobileResponse, emailResponse] + const initResult = IncomingEncryptSubmission.init( + { + responseMode: ResponseMode.Encrypt, + form_fields: [mobileField, emailField], + } as unknown as IPopulatedEncryptedForm, + responses, + '', + ) + expect(initResult._unsafeUnwrap().responses).toEqual(responses) + }) + + it('should fail when responses are missing', () => { + mockCheckIsEncryptedEncoding.mockReturnValueOnce(ok(true)) + const mobileField = generateDefaultField(BasicField.Mobile) + const emailField = generateDefaultField(BasicField.Email) + const mobileResponse = generateSingleAnswerResponse( + mobileField, + '+6587654321', + ) + const responses = [mobileResponse] + const initResult = IncomingEncryptSubmission.init( + { + responseMode: ResponseMode.Encrypt, + form_fields: [mobileField, emailField], + } as unknown as IPopulatedEncryptedForm, + responses, + '', + ) + expect(initResult._unsafeUnwrapErr()).toEqual( + new ConflictError('Some form fields are missing'), + ) + }) +}) diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts index 1691f417c7..fe8298ede0 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts @@ -19,7 +19,6 @@ import { import { createLoggerWithLabel } from '../../../config/logger' import { getEncryptSubmissionModel } from '../../../models/submission.server.model' import { CaptchaFactory } from '../../../services/captcha/captcha.factory' -import { checkIsEncryptedEncoding } from '../../../utils/encryption' import { createReqMeta, getRequestIp } from '../../../utils/request' import { getFormAfterPermissionChecks } from '../../auth/auth.service' import { @@ -34,10 +33,8 @@ import { getPopulatedUserById } from '../../user/user.service' import { VerifiedContentFactory } from '../../verified-content/verified-content.factory' import { WebhookFactory } from '../../webhook/webhook.factory' import * as EncryptSubmissionMiddleware from '../encrypt-submission/encrypt-submission.middleware' -import { - getProcessedResponses, - sendEmailConfirmations, -} from '../submission.service' +import { sendEmailConfirmations } from '../submission.service' +import { extractEmailConfirmationDataFromIncomingSubmission } from '../submission.utils' import { checkFormIsEncryptMode, @@ -53,6 +50,7 @@ import { createEncryptedSubmissionDto, mapRouteError, } from './encrypt-submission.utils' +import IncomingEncryptSubmission from './IncomingEncryptSubmission.class' const logger = createLoggerWithLabel(module) const EncryptSubmission = getEncryptSubmissionModel(mongoose) @@ -164,41 +162,23 @@ const submitEncryptModeForm: ControllerHandler< }) } - // Validate encrypted submission + // Create Incoming Submission const { encryptedContent, responses } = req.body - const encryptedEncodingResult = await checkIsEncryptedEncoding( + const incomingSubmissionResult = IncomingEncryptSubmission.init( + form, + responses, encryptedContent, ) - if (encryptedEncodingResult.isErr()) { - logger.error({ - message: 'Error verifying content has encrypted encoding.', - meta: logMeta, - error: encryptedEncodingResult.error, - }) + if (incomingSubmissionResult.isErr()) { const { statusCode, errorMessage } = mapRouteError( - encryptedEncodingResult.error, + incomingSubmissionResult.error, ) return res.status(statusCode).json({ message: errorMessage, }) } + const incomingSubmission = incomingSubmissionResult.value - // Process encrypted submission - const processedResponsesResult = await getProcessedResponses(form, responses) - if (processedResponsesResult.isErr()) { - logger.error({ - message: 'Error processing encrypted submission.', - meta: logMeta, - error: processedResponsesResult.error, - }) - const { statusCode, errorMessage } = mapRouteError( - processedResponsesResult.error, - ) - return res.status(statusCode).json({ - message: errorMessage, - }) - } - const processedResponses = processedResponsesResult.value delete (req.body as SetOptional).responses // Checks if user is SPCP-authenticated before allowing submission @@ -301,7 +281,6 @@ const submitEncryptModeForm: ControllerHandler< } // Save Responses to Database - const formData = req.body.encryptedContent let attachmentMetadata = new Map() if (req.body.attachments) { @@ -326,7 +305,7 @@ const submitEncryptModeForm: ControllerHandler< form: form._id, authType: form.authType, myInfoFields: form.getUniqueMyInfoAttrs(), - encryptedContent: formData, + encryptedContent: incomingSubmission.encryptedContent, verifiedContent: verified, attachmentMetadata, version: req.body.version, @@ -379,8 +358,9 @@ const submitEncryptModeForm: ControllerHandler< return sendEmailConfirmations({ form, - parsedResponses: processedResponses, submission: savedSubmission, + recipientData: + extractEmailConfirmationDataFromIncomingSubmission(incomingSubmission), }).mapErr((error) => { logger.error({ message: 'Error while sending email confirmations', diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts index c1f99b8203..21ff1a1e17 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.utils.ts @@ -1,7 +1,11 @@ import { StatusCodes } from 'http-status-codes' import moment from 'moment-timezone' -import { EncryptedSubmissionDto, SubmissionData } from '../../../../types' +import { + EncryptedSubmissionDto, + MapRouteErrors, + SubmissionData, +} from '../../../../types' import { MapRouteError } from '../../../../types/routing' import { createLoggerWithLabel } from '../../../config/logger' import { MalformedVerifiedContentError } from '../../../modules/verified-content/verified-content.errors' @@ -10,12 +14,14 @@ import { MissingCaptchaError, VerifyCaptchaError, } from '../../../services/captcha/captcha.errors' +import { genericMapRouteErrorTransform } from '../../../utils/error' import { AttachmentUploadError, DatabaseConflictError, DatabaseError, DatabasePayloadSizeError, DatabaseValidationError, + EmptyErrorFieldError, MalformedParametersError, MissingFeatureError, } from '../../core/core.errors' @@ -51,7 +57,7 @@ const logger = createLoggerWithLabel(module) * messages. * @param error The error to retrieve the status codes and error messages */ -export const mapRouteError: MapRouteError = ( +const errorMapper: MapRouteError = ( error, coreErrorMessage = 'Sorry, something went wrong. Please try again.', ) => { @@ -172,6 +178,7 @@ export const mapRouteError: MapRouteError = ( } case CreatePresignedUrlError: case DatabaseError: + case EmptyErrorFieldError: return { statusCode: StatusCodes.INTERNAL_SERVER_ERROR, errorMessage: error.message, @@ -192,6 +199,9 @@ export const mapRouteError: MapRouteError = ( } } +export const mapRouteError: MapRouteErrors = + genericMapRouteErrorTransform(errorMapper) + /** * Creates and returns an EncryptedSubmissionDto object from submissionData and * attachment presigned urls. @@ -208,5 +218,6 @@ export const createEncryptedSubmissionDto = ( content: submissionData.encryptedContent, verified: submissionData.verifiedContent, attachmentMetadata: attachmentPresignedUrls, + version: submissionData.version, } } diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts index ac4447eb25..1dedd9b20f 100644 --- a/src/app/modules/submission/submission.service.ts +++ b/src/app/modules/submission/submission.service.ts @@ -1,4 +1,3 @@ -import _ from 'lodash' import mongoose from 'mongoose' import { err, errAsync, ok, okAsync, Result, ResultAsync } from 'neverthrow' @@ -19,6 +18,7 @@ import { import { createLoggerWithLabel } from '../../config/logger' import getSubmissionModel from '../../models/submission.server.model' import MailService from '../../services/mail/mail.service' +import { AutoReplyMailData } from '../../services/mail/mail.types' import { createQueryWithDateParam, isMalformedDate } from '../../utils/date' import { validateField } from '../../utils/field-validation' import { DatabaseError, MalformedParametersError } from '../core/core.errors' @@ -30,50 +30,11 @@ import { ValidateFieldError, } from './submission.errors' import { ProcessedFieldResponse } from './submission.types' -import { extractEmailConfirmationData, getModeFilter } from './submission.utils' +import { getFilteredResponses } from './submission.utils' const logger = createLoggerWithLabel(module) const SubmissionModel = getSubmissionModel(mongoose) -/** - * Filter allowed form field responses from given responses and return the - * array of responses with duplicates removed. - * - * @param form The form document - * @param responses the responses that corresponds to the given form - * @returns neverthrow ok() filtered list of allowed responses with duplicates (if any) removed - * @returns neverthrow err(ConflictError) if the given form's form field ids count do not match given responses' - */ -const getFilteredResponses = ( - form: IFormDocument, - responses: FieldResponse[], -): Result => { - const modeFilter = getModeFilter(form.responseMode) - - if (!form.form_fields) { - return err(new ConflictError('Form fields are missing')) - } - // _id must be transformed to string as form response is jsonified. - const fieldIds = modeFilter(form.form_fields).map((field) => ({ - _id: String(field._id), - })) - const uniqueResponses = _.uniqBy(modeFilter(responses), '_id') - const results = _.intersectionBy(uniqueResponses, fieldIds, '_id') - - if (results.length < fieldIds.length) { - const onlyInForm = _.differenceBy(fieldIds, results, '_id').map( - ({ _id }) => _id, - ) - return err( - new ConflictError('Some form fields are missing', { - formId: form._id, - onlyInForm, - }), - ) - } - return ok(results) -} - /** * Injects response metadata such as the question, visibility state. In * addition, validation such as input validation or signature validation on @@ -221,43 +182,39 @@ export const getFormSubmissionsCount = ( * @param param0 Data to include in email confirmations * @param param0.form Form object * @param param0.submission Submission object which was saved to database - * @param param0.parsedResponses Responses for each field - * @param param0.autoReplyData Subset of responses to be included in email confirmation + * @param param0.responsesData Subset of responses to be included in email confirmation * @param param0.attachments Attachments to be included in email + * @param recipientData Array of objects that contains autoreply mail data to override with defaults * @returns ok(true) if all emails were sent successfully * @returns err(SendEmailConfirmationError) if any email failed to be sent */ export const sendEmailConfirmations = ({ form, submission, - parsedResponses, - autoReplyData, + responsesData = [], attachments, + recipientData, }: { form: IPopulatedForm submission: S - parsedResponses: ProcessedFieldResponse[] - autoReplyData?: EmailRespondentConfirmationField[] + responsesData?: EmailRespondentConfirmationField[] attachments?: IAttachmentInfo[] + recipientData: AutoReplyMailData[] }): ResultAsync => { const logMeta = { action: 'sendEmailConfirmations', formId: form._id, submissionid: submission._id, } - const confirmationData = extractEmailConfirmationData( - parsedResponses, - form.form_fields, - ) - if (confirmationData.length === 0) { + if (recipientData.length === 0) { return okAsync(true) } const sentEmailsPromise = MailService.sendAutoReplyEmails({ form, submission, attachments, - responsesData: autoReplyData ?? [], - autoReplyMailDatas: confirmationData, + responsesData, + autoReplyMailDatas: recipientData, }) return ResultAsync.fromPromise(sentEmailsPromise, (error) => { logger.error({ diff --git a/src/app/modules/submission/submission.types.ts b/src/app/modules/submission/submission.types.ts index c05d2dc59d..4db1cdfc6f 100644 --- a/src/app/modules/submission/submission.types.ts +++ b/src/app/modules/submission/submission.types.ts @@ -1,11 +1,14 @@ +import { Opaque } from 'type-fest' + import { + FieldResponse, IAttachmentResponse, ICheckboxResponse, ISingleAnswerResponse, ITableResponse, } from 'src/types/response' -import { BasicField } from '../../../types/field' +import { BasicField, IFieldSchema } from '../../../types/field' export type ProcessedResponse = { question: string @@ -13,6 +16,29 @@ export type ProcessedResponse = { isUserVerified?: boolean } +/** + * Represents a field map that is guaranteed to contain the id of + * ALL field responses in an incoming submission. + */ +export type ValidatedFieldMap = Opaque< + { [p: string]: IFieldSchema }, + 'ValidatedFieldMap' +> + +export type VisibleResponseIdSet = Opaque, 'VisibleResponseIdSet'> + +export type VerifiableResponseIdSet = Opaque< + Set, + 'VerifiableResponseIdSet' +> + +/** + * Represents a response allowed by `getModeFilter`. When presented as a + * list, additionally guarantees that duplicates (if any) are removed. + * Instantiated ONLY via `getFilteredResponses`. + */ +export type FilteredResponse = Opaque + export type ColumnResponse = { fieldType: BasicField answer: string diff --git a/src/app/modules/submission/submission.utils.ts b/src/app/modules/submission/submission.utils.ts index 4a248dc0a6..e9754cb8af 100644 --- a/src/app/modules/submission/submission.utils.ts +++ b/src/app/modules/submission/submission.utils.ts @@ -1,11 +1,20 @@ -import { keyBy } from 'lodash' +import { differenceBy, intersectionBy, keyBy, uniqBy } from 'lodash' +import { err, ok, Result } from 'neverthrow' import { FIELDS_TO_REJECT } from '../../../shared/resources/basic' -import { BasicField, IFieldSchema, ResponseMode } from '../../../types' +import { + BasicField, + FieldResponse, + IFieldSchema, + IFormDocument, + ResponseMode, +} from '../../../types' import { isEmailField } from '../../../types/field/utils/guards' import { AutoReplyMailData } from '../../services/mail/mail.types' -import { ProcessedFieldResponse } from './submission.types' +import { IncomingSubmission } from './IncomingSubmission.class' +import { ConflictError } from './submission.errors' +import { FilteredResponse } from './submission.types' type ModeFilterParam = { fieldType: BasicField @@ -37,16 +46,17 @@ const encryptModeFilter = (responses: T[] = []) => { /** * Extracts response data to be sent in email confirmations - * @param parsedResponses Responses from form filler + * @param responses Responses from form filler * @param formFields Fields from form object * @returns Array of data for email confirmations */ +// TODO: Migrate to extractEmailConfirmationDataFromIncomingSubmission export const extractEmailConfirmationData = ( - parsedResponses: ProcessedFieldResponse[], + responses: FieldResponse[], formFields: IFieldSchema[] | undefined, ): AutoReplyMailData[] => { const fieldsById = keyBy(formFields, '_id') - return parsedResponses.reduce((acc, response) => { + return responses.reduce((acc, response) => { const field = fieldsById[response._id] if ( field && @@ -68,3 +78,55 @@ export const extractEmailConfirmationData = ( return acc }, []) } + +/** + * Extracts response data to be sent in email confirmations + * @param responses Responses from form filler + * @param formFields Fields from form object + * @returns Array of data for email confirmations + */ +export const extractEmailConfirmationDataFromIncomingSubmission = ( + incomingSubmission: IncomingSubmission, +): AutoReplyMailData[] => { + const { responses, form } = incomingSubmission + return extractEmailConfirmationData(responses, form.form_fields) +} + +/** + * Filter allowed form field responses from given responses and return the + * array of responses with duplicates removed. + * + * @param form The form document + * @param responses the responses that corresponds to the given form + * @returns neverthrow ok() filtered list of allowed responses with duplicates (if any) removed + * @returns neverthrow err(ConflictError) if the given form's form field ids count do not match given responses' + */ +export const getFilteredResponses = ( + form: IFormDocument, + responses: FieldResponse[], +): Result => { + const modeFilter = getModeFilter(form.responseMode) + + if (!form.form_fields) { + return err(new ConflictError('Form fields are missing')) + } + // _id must be transformed to string as form response is jsonified. + const fieldIds = modeFilter(form.form_fields).map((field) => ({ + _id: String(field._id), + })) + const uniqueResponses = uniqBy(modeFilter(responses), '_id') + const results = intersectionBy(uniqueResponses, fieldIds, '_id') + + if (results.length < fieldIds.length) { + const onlyInForm = differenceBy(fieldIds, results, '_id').map( + ({ _id }) => _id, + ) + return err( + new ConflictError('Some form fields are missing', { + formId: form._id, + onlyInForm, + }), + ) + } + return ok(results as FilteredResponse[]) +} diff --git a/src/app/modules/webhook/__tests__/webhook.service.spec.ts b/src/app/modules/webhook/__tests__/webhook.service.spec.ts index c763f183aa..8ecb70678c 100644 --- a/src/app/modules/webhook/__tests__/webhook.service.spec.ts +++ b/src/app/modules/webhook/__tests__/webhook.service.spec.ts @@ -94,6 +94,9 @@ describe('webhook.service', () => { formId: MOCK_FORM_ID, submissionId: MOCK_SUBMISSION_ID, verifiedContent: 'mockVerifiedContent', + attachmentDownloadUrls: { + 'some-field-id': 'https://mock.s3.url/some/s3/url/timeout=3600', + }, version: 1, }, } diff --git a/src/app/modules/webhook/webhook.errors.ts b/src/app/modules/webhook/webhook.errors.ts index 426d32f170..6ecc52f3d7 100644 --- a/src/app/modules/webhook/webhook.errors.ts +++ b/src/app/modules/webhook/webhook.errors.ts @@ -12,6 +12,20 @@ export class WebhookValidationError extends ApplicationError { } } +/** + * Webhook failed to generate S3 presigned URLs for attachments + */ +export class WebhookFailedWithPresignedUrlGenerationError extends ApplicationError { + meta: { + originalError: unknown + } + + constructor(error: unknown, message = 'Presigned Url Generation failed') { + super(message) + this.meta = { originalError: error } + } +} + /** * Webhook returned non-200 status, but error is not instance of AxiosError */ diff --git a/src/app/modules/webhook/webhook.service.ts b/src/app/modules/webhook/webhook.service.ts index be3f7feb19..ce6e16b69f 100644 --- a/src/app/modules/webhook/webhook.service.ts +++ b/src/app/modules/webhook/webhook.service.ts @@ -1,4 +1,5 @@ import axios from 'axios' +import Bluebird from 'bluebird' import { get } from 'lodash' import mongoose from 'mongoose' import { errAsync, okAsync, ResultAsync } from 'neverthrow' @@ -9,6 +10,7 @@ import { IWebhookResponse, WebhookView, } from '../../../types' +import { aws as AwsConfig } from '../../config/config' import formsgSdk from '../../config/formsg-sdk' import { createLoggerWithLabel } from '../../config/logger' import { getEncryptSubmissionModel } from '../../models/submission.server.model' @@ -18,6 +20,7 @@ import { SubmissionNotFoundError } from '../submission/submission.errors' import { WebhookFailedWithAxiosError, + WebhookFailedWithPresignedUrlGenerationError, WebhookFailedWithUnknownError, WebhookPushToQueueError, WebhookValidationError, @@ -72,10 +75,35 @@ export const saveWebhookRecord = ( }) } +const createWebhookSubmissionView = ( + submissionWebhookView: WebhookView, +): Promise => { + // Generate S3 signed urls + const signedUrlPromises: Record> = {} + for (const key in submissionWebhookView.data.attachmentDownloadUrls) { + signedUrlPromises[key] = AwsConfig.s3.getSignedUrlPromise('getObject', { + Bucket: AwsConfig.attachmentS3Bucket, + Key: submissionWebhookView.data.attachmentDownloadUrls[key], + Expires: 60 * 60, // one hour expiry + }) + } + + return Bluebird.props(signedUrlPromises).then((signedUrls) => { + submissionWebhookView.data.attachmentDownloadUrls = signedUrls + return submissionWebhookView + }) +} + export const sendWebhook = ( webhookView: WebhookView, webhookUrl: string, -): ResultAsync => { +): ResultAsync< + IWebhookResponse, + | WebhookValidationError + | WebhookFailedWithAxiosError + | WebhookFailedWithPresignedUrlGenerationError + | WebhookFailedWithUnknownError +> => { const now = Date.now() const { submissionId, formId } = webhookView.data @@ -104,77 +132,93 @@ export const sendWebhook = ( return error instanceof WebhookValidationError ? error : new WebhookValidationError() - }) - .andThen(() => - ResultAsync.fromPromise( - axios.post(webhookUrl, webhookView, { - headers: { - 'X-FormSG-Signature': formsgSdk.webhooks.constructHeader({ - epoch: now, - submissionId, - formId, - signature, - }), - }, - maxRedirects: 0, - // Timeout after 10 seconds to allow for cold starts in receiver, - // e.g. Lambdas - timeout: 10 * 1000, - }), - (error) => { - logger.error({ - message: 'Webhook POST failed', - meta: { - ...logMeta, - isAxiosError: axios.isAxiosError(error), - status: get(error, 'response.status'), - }, - error, - }) - if (axios.isAxiosError(error)) { - return new WebhookFailedWithAxiosError(error) - } - return new WebhookFailedWithUnknownError(error) - }, - ), + }).andThen(() => { + return ResultAsync.fromPromise( + createWebhookSubmissionView(webhookView), + (error) => { + logger.error({ + message: 'S3 attachment presigned URL generation failed', + meta: logMeta, + error, + }) + return new WebhookFailedWithPresignedUrlGenerationError(error) + }, ) - .map((response) => { - // Capture response for logging purposes - logger.info({ - message: 'Webhook POST succeeded', - meta: { - ...logMeta, - status: get(response, 'status'), - }, + .andThen((submissionWebhookView) => + ResultAsync.fromPromise( + axios.post(webhookUrl, submissionWebhookView, { + headers: { + 'X-FormSG-Signature': formsgSdk.webhooks.constructHeader({ + epoch: now, + submissionId, + formId, + signature, + }), + }, + maxRedirects: 0, + // Timeout after 10 seconds to allow for cold starts in receiver, + // e.g. Lambdas + timeout: 10 * 1000, + }), + (error) => { + logger.error({ + message: 'Webhook POST failed', + meta: { + ...logMeta, + isAxiosError: axios.isAxiosError(error), + status: get(error, 'response.status'), + }, + error, + }) + if (axios.isAxiosError(error)) { + return new WebhookFailedWithAxiosError(error) + } + return new WebhookFailedWithUnknownError(error) + }, + ), + ) + .map((response) => { + // Capture response for logging purposes + logger.info({ + message: 'Webhook POST succeeded', + meta: { + ...logMeta, + status: get(response, 'status'), + }, + }) + return { + signature, + webhookUrl, + response: formatWebhookResponse(response), + } }) - return { - signature, - webhookUrl, - response: formatWebhookResponse(response), - } - }) - .orElse((error) => { - // Webhook was not posted - if (error instanceof WebhookValidationError) return errAsync(error) + .orElse((error) => { + // Webhook was not posted + if (error instanceof WebhookValidationError) return errAsync(error) + + // S3 pre-signed URL generation failed + if (error instanceof WebhookFailedWithPresignedUrlGenerationError) + return errAsync(error) + + // Webhook was posted but failed + if (error instanceof WebhookFailedWithUnknownError) { + return okAsync({ + signature, + webhookUrl, + // Not Axios error so no guarantee of having response. + // Hence allow formatting function to return default shape. + response: formatWebhookResponse(), + }) + } - // Webhook was posted but failed - if (error instanceof WebhookFailedWithUnknownError) { + const axiosError = error.meta.originalError return okAsync({ signature, webhookUrl, - // Not Axios error so no guarantee of having response. - // Hence allow formatting function to return default shape. - response: formatWebhookResponse(), + response: formatWebhookResponse(axiosError.response), }) - } - - const axiosError = error.meta.originalError - return okAsync({ - signature, - webhookUrl, - response: formatWebhookResponse(axiosError.response), }) - }) + }) } /** diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.settings.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.settings.routes.spec.ts index b5b7f3546a..d100bb20f4 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.settings.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.settings.routes.spec.ts @@ -301,7 +301,9 @@ describe('admin-form.settings.routes', () => { }) const session = await createAuthedSession(fakeUser.email, request) const expectedResponse = jsonParseStringify({ - message: `User ${fakeUser.email} not authorized to perform write operation on Form ${form._id} with title: ${form.title}.`, + message: `User ${fakeUser.email.toLowerCase()} not authorized to perform write operation on Form ${ + form._id + } with title: ${form.title}.`, }) // Act @@ -431,12 +433,14 @@ describe('admin-form.settings.routes', () => { }, }) const fakeUser = await dbHandler.insertUser({ - mailName: 'fakeUser', + mailName: 'userWithoutReadPermissions', agencyId: new ObjectId(), }) const session = await createAuthedSession(fakeUser.email, request) const expectedResponse = jsonParseStringify({ - message: `User ${fakeUser.email} not authorized to perform read operation on Form ${form._id} with title: ${form.title}.`, + message: `User ${fakeUser.email.toLowerCase()} not authorized to perform read operation on Form ${ + form._id + } with title: ${form.title}.`, }) // Act diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts index 4b1f5fa894..b83cfc8f02 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts @@ -535,7 +535,7 @@ describe('admin-form.submissions.routes', () => { // Arrange const submissions = await Promise.all( times(11, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -571,6 +571,7 @@ describe('admin-form.submissions.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .sort((a, b) => String(a._id).localeCompare(String(b._id))) @@ -591,7 +592,7 @@ describe('admin-form.submissions.routes', () => { // Arrange const submissions = await Promise.all( times(5, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -627,6 +628,7 @@ describe('admin-form.submissions.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .sort((a, b) => String(a._id).localeCompare(String(b._id))) @@ -655,7 +657,7 @@ describe('admin-form.submissions.routes', () => { // Arrange const submissions = await Promise.all( times(5, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -705,6 +707,7 @@ describe('admin-form.submissions.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .filter((s) => expectedSubmissionIds.includes(s._id)) @@ -726,7 +729,7 @@ describe('admin-form.submissions.routes', () => { // Arrange const submissions = await Promise.all( times(5, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -777,6 +780,7 @@ describe('admin-form.submissions.routes', () => { encryptedContent: s.encryptedContent, verifiedContent: s.verifiedContent, created: s.created, + version: s.version, }), ) .filter((s) => expectedSubmissionIds.includes(s._id)) @@ -928,7 +932,7 @@ describe('admin-form.submissions.routes', () => { encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', } - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, ...expectedSubmissionParams, }) @@ -948,6 +952,7 @@ describe('admin-form.submissions.routes', () => { refNo: String(submission._id), submissionTime: expect.any(String), verified: expectedSubmissionParams.verifiedContent, + version: submission.version, }) }) @@ -961,7 +966,7 @@ describe('admin-form.submissions.routes', () => { ['fieldId2', 'some.other.attachment.url'], ]), } - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, ...expectedSubmissionParams, }) @@ -988,6 +993,7 @@ describe('admin-form.submissions.routes', () => { refNo: String(submission._id), submissionTime: expect.any(String), verified: expectedSubmissionParams.verifiedContent, + version: submission.version, }) }) @@ -1139,7 +1145,7 @@ describe('admin-form.submissions.routes', () => { jest .spyOn(EncryptSubmissionModel, 'findEncryptedSubmissionById') .mockRejectedValueOnce(new Error('ohno')) - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', @@ -1168,7 +1174,7 @@ describe('admin-form.submissions.routes', () => { .spyOn(aws.s3, 'getSignedUrlPromise') .mockRejectedValueOnce(new Error('something went wrong')) - const submission = await createSubmission({ + const submission = await createEncryptSubmission({ form: defaultForm, encryptedContent: 'any encrypted content', verifiedContent: 'any verified content', @@ -1223,7 +1229,7 @@ describe('admin-form.submissions.routes', () => { // Create 11 submissions const submissions = await Promise.all( times(11, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -1258,7 +1264,7 @@ describe('admin-form.submissions.routes', () => { it('should return 200 with empty results if query.page does not have metadata', async () => { // Arrange // Create single submission - await createSubmission({ + await createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content`, verifiedContent: `any verified content`, @@ -1286,7 +1292,7 @@ describe('admin-form.submissions.routes', () => { // Create 3 submissions const submissions = await Promise.all( times(3, (count) => - createSubmission({ + createEncryptSubmission({ form: defaultForm, encryptedContent: `any encrypted content ${count}`, verifiedContent: `any verified content ${count}`, @@ -1463,7 +1469,7 @@ describe('admin-form.submissions.routes', () => { }) // Helper utils -const createSubmission = ({ +const createEncryptSubmission = ({ form, encryptedContent, verifiedContent, @@ -1476,7 +1482,7 @@ const createSubmission = ({ verifiedContent?: string created?: Date }) => { - return SubmissionModel.create({ + return EncryptSubmissionModel.create({ submissionType: SubmissionType.Encrypt, form: form._id, authType: form.authType, diff --git a/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts b/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts index 2dc1afb484..ea61f57964 100644 --- a/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts +++ b/src/app/routes/api/v3/admin/forms/admin-forms.routes.ts @@ -1,7 +1,6 @@ import { Router } from 'express' import { - denyRpSpStudentEmails, logAdminAction, withUserAuthentication, } from '../../../../../modules/auth/auth.middlewares' @@ -18,7 +17,6 @@ export const AdminFormsRouter = Router() // All routes in this handler should be protected by authentication. AdminFormsRouter.use(withUserAuthentication) -AdminFormsRouter.use(denyRpSpStudentEmails) // Log all non-get admin form actions AdminFormsRouter.use('/:formId([a-fA-F0-9]{24})', logAdminAction) diff --git a/src/app/routes/api/v3/auth/__tests__/auth.routes.spec.ts b/src/app/routes/api/v3/auth/__tests__/auth.routes.spec.ts index d69a1d18f7..d6aed891d9 100644 --- a/src/app/routes/api/v3/auth/__tests__/auth.routes.spec.ts +++ b/src/app/routes/api/v3/auth/__tests__/auth.routes.spec.ts @@ -96,6 +96,23 @@ describe('auth.routes', () => { expect(response.text).toEqual('OK') }) + it('should return 200 when domain of body.email has a case-insensitive match in Agency collection', async () => { + // Arrange + // Insert agency + const validDomain = 'example.com' + const validEmail = `test@${validDomain}` + await dbHandler.insertAgency({ mailDomain: validDomain }) + + // Act + const response = await request + .post('/auth/email/validate') + .send({ email: validEmail.toUpperCase() }) + + // Assert + expect(response.status).toEqual(200) + expect(response.text).toEqual('OK') + }) + it('should return 500 when validating domain returns a database error', async () => { // Arrange // Insert agency @@ -250,6 +267,23 @@ describe('auth.routes', () => { expect(response.status).toEqual(200) expect(response.body).toEqual(`OTP sent to ${VALID_EMAIL}`) }) + + it('should return 200 when otp is sent successfully and email is non-lowercase', async () => { + // Arrange + const sendLoginOtpSpy = jest + .spyOn(MailService, 'sendLoginOtp') + .mockReturnValueOnce(okAsync(true)) + + // Act + const response = await request + .post('/auth/otp/generate') + .send({ email: VALID_EMAIL.toUpperCase() }) + + // Assert + expect(sendLoginOtpSpy).toHaveBeenCalled() + expect(response.status).toEqual(200) + expect(response.body).toEqual(`OTP sent to ${VALID_EMAIL}`) + }) }) describe('POST /auth/otp/verify', () => { @@ -475,6 +509,33 @@ describe('auth.routes', () => { expect(sessionCookie).toBeDefined() }) + it('should return 200 with user object when body.otp is a valid OTP and body.email is non-lowercase', async () => { + // Arrange + // Request for OTP so the hash exists. + await requestForOtp(VALID_EMAIL) + + // Act + const response = await request + .post('/auth/otp/verify') + .send({ email: VALID_EMAIL.toUpperCase(), otp: MOCK_VALID_OTP }) + + // Assert + expect(response.status).toEqual(200) + // Body should be an user object. + expect(response.body).toMatchObject({ + // Required since that's how the data is sent out from the application. + agency: JSON.parse(JSON.stringify(defaultAgency.toObject())), + _id: expect.any(String), + created: expect.any(String), + email: VALID_EMAIL, + }) + // Should have session cookie returned. + const sessionCookie = request.cookies.find( + (cookie) => cookie.name === 'connect.sid', + ) + expect(sessionCookie).toBeDefined() + }) + it('should return 500 when upserting user document fails', async () => { // Arrange // Request for OTP so the hash exists. diff --git a/src/app/routes/api/v3/auth/auth.routes.ts b/src/app/routes/api/v3/auth/auth.routes.ts index bf144dc9fb..eedc25fff8 100644 --- a/src/app/routes/api/v3/auth/auth.routes.ts +++ b/src/app/routes/api/v3/auth/auth.routes.ts @@ -21,7 +21,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), }), }), AuthController.handleCheckUser, @@ -47,7 +48,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), }), }), AuthController.handleLoginSendOtp, @@ -73,7 +75,8 @@ AuthRouter.post( email: Joi.string() .required() .email() - .message('Please enter a valid email'), + .message('Please enter a valid email') + .lowercase(), otp: Joi.string() .required() .regex(/^\d{6}$/) diff --git a/src/app/routes/api/v3/client/client.routes.ts b/src/app/routes/api/v3/client/client.routes.ts index 2d3d0a7c4b..521506d91a 100644 --- a/src/app/routes/api/v3/client/client.routes.ts +++ b/src/app/routes/api/v3/client/client.routes.ts @@ -2,7 +2,6 @@ import { celebrate, Joi, Segments } from 'celebrate' import { Router } from 'express' import * as FrontendServerController from '../../../../modules/frontend/frontend.controller' -import { GoogleAnalyticsFactory } from '../../../../modules/frontend/google-analytics.factory' export const ClientRouter = Router() @@ -15,7 +14,7 @@ export const ClientRouter = Router() */ ClientRouter.get( '/analytics/google', - GoogleAnalyticsFactory.addGoogleAnalyticsData, + FrontendServerController.addGoogleAnalyticsData, ) /** @@ -30,6 +29,8 @@ ClientRouter.get('/environment', FrontendServerController.addEnvVarData) * Generate a json of current activated features * @route GET /api/v3/client/features * @return json with featureManager.states + * @deprecated + * TODO (#2147): delete this */ ClientRouter.get('/features', FrontendServerController.showFeaturesStates) diff --git a/src/app/services/intranet/__tests__/intranet.factory.spec.ts b/src/app/services/intranet/__tests__/intranet.factory.spec.ts deleted file mode 100644 index 83669d3d01..0000000000 --- a/src/app/services/intranet/__tests__/intranet.factory.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { mocked } from 'ts-jest/utils' - -import { FeatureNames } from 'src/app/config/feature-manager' -import { MissingFeatureError } from 'src/app/modules/core/core.errors' - -import { createIntranetFactory } from '../intranet.factory' -import { IntranetService } from '../intranet.service' - -jest.mock('../intranet.service') -const MockIntranetService = mocked(IntranetService, true) - -const MOCK_INTRANET_PROPS = { - intranetIpListPath: 'somePath', -} - -describe('intranet.factory', () => { - afterEach(() => jest.clearAllMocks()) - - it('should call the IntranetService constructor when Intranet feature is enabled', () => { - createIntranetFactory({ - isEnabled: true, - props: MOCK_INTRANET_PROPS, - }) - - expect(MockIntranetService).toHaveBeenCalledWith(MOCK_INTRANET_PROPS) - }) - - it('should return error functions when isEnabled is false', () => { - const intranetFactory = createIntranetFactory({ - isEnabled: false, - props: MOCK_INTRANET_PROPS, - }) - - expect(MockIntranetService).not.toHaveBeenCalled() - expect(intranetFactory.isIntranetIp('')._unsafeUnwrapErr()).toEqual( - new MissingFeatureError(FeatureNames.Intranet), - ) - }) - - it('should return error functions when props is undefined', () => { - const intranetFactory = createIntranetFactory({ - isEnabled: true, - }) - - expect(MockIntranetService).not.toHaveBeenCalled() - expect(intranetFactory.isIntranetIp('')._unsafeUnwrapErr()).toEqual( - new MissingFeatureError(FeatureNames.Intranet), - ) - }) -}) diff --git a/src/app/services/intranet/__tests__/intranet.service.spec.ts b/src/app/services/intranet/__tests__/intranet.service.spec.ts index ed4db49f4c..73872e361d 100644 --- a/src/app/services/intranet/__tests__/intranet.service.spec.ts +++ b/src/app/services/intranet/__tests__/intranet.service.spec.ts @@ -2,34 +2,15 @@ import fs from 'fs' import { IntranetService } from '../intranet.service' -const MOCK_IP_LIST = ['1.2.3.4', '5.6.7.8'] -const MOCK_IP_LIST_FILE = Buffer.from(MOCK_IP_LIST.join('\n')) -const MOCK_IP_LIST_PATH = '../some/path' - -jest.mock('fs', () => ({ - ...(jest.requireActual('fs') as typeof fs), - readFileSync: jest.fn().mockImplementation(() => MOCK_IP_LIST_FILE), -})) +const MOCK_IP_LIST_PATH = 'tests/mock-intranet-ips.txt' +const MOCK_IP_LIST = fs.readFileSync(MOCK_IP_LIST_PATH).toString().split('\n') describe('IntranetService', () => { - const intranetService = new IntranetService({ - intranetIpListPath: MOCK_IP_LIST_PATH, - }) - afterEach(() => jest.clearAllMocks()) - describe('constructor', () => { - it('should instantiate without errors', () => { - const intranetService = new IntranetService({ - intranetIpListPath: MOCK_IP_LIST_PATH, - }) - - expect(intranetService).toBeTruthy() - }) - }) describe('isIntranetIp', () => { it('should return true when IP is in intranet IP list', () => { - const result = intranetService.isIntranetIp(MOCK_IP_LIST[0]) + const result = IntranetService.isIntranetIp(MOCK_IP_LIST[0]) expect(result).toBe(true) }) @@ -37,7 +18,7 @@ describe('IntranetService', () => { it('should return false when IP is not in intranet IP list', () => { const ipNotInList = '10.20.30.40' - const result = intranetService.isIntranetIp(ipNotInList) + const result = IntranetService.isIntranetIp(ipNotInList) expect(result).toBe(false) }) diff --git a/src/app/services/intranet/intranet.factory.ts b/src/app/services/intranet/intranet.factory.ts deleted file mode 100644 index 97c8cdd92b..0000000000 --- a/src/app/services/intranet/intranet.factory.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { err, ok, Result } from 'neverthrow' - -import FeatureManager, { - FeatureNames, - RegisteredFeature, -} from '../../config/feature-manager' -import { MissingFeatureError } from '../../modules/core/core.errors' - -import { IntranetService } from './intranet.service' - -interface IIntranetFactory { - isIntranetIp: (ip: string) => Result -} - -export const createIntranetFactory = ({ - isEnabled, - props, -}: RegisteredFeature): IIntranetFactory => { - if (isEnabled && props?.intranetIpListPath) { - const intranetService = new IntranetService(props) - return { - isIntranetIp: (ip: string) => ok(intranetService.isIntranetIp(ip)), - } - } - - const error = new MissingFeatureError(FeatureNames.Intranet) - return { - isIntranetIp: () => err(error), - } -} - -const intranetFeature = FeatureManager.get(FeatureNames.Intranet) -export const IntranetFactory = createIntranetFactory(intranetFeature) diff --git a/src/app/services/intranet/intranet.middleware.ts b/src/app/services/intranet/intranet.middleware.ts index 57ef0dd43b..4a34e96f58 100644 --- a/src/app/services/intranet/intranet.middleware.ts +++ b/src/app/services/intranet/intranet.middleware.ts @@ -2,14 +2,14 @@ import { createLoggerWithLabel } from '../../config/logger' import { ControllerHandler } from '../../modules/core/core.types' import { createReqMeta, getRequestIp } from '../../utils/request' -import { IntranetFactory } from './intranet.factory' +import { IntranetService } from './intranet.service' const logger = createLoggerWithLabel(module) export const logIntranetUsage: ControllerHandler = (req, _res, next) => { - const isIntranetResult = IntranetFactory.isIntranetIp(getRequestIp(req)) + const isIntranet = IntranetService.isIntranetIp(getRequestIp(req)) // Ignore case where result is err, as this means intranet feature is not enabled - if (isIntranetResult.isOk() && isIntranetResult.value) { + if (isIntranet) { logger.info({ message: 'Request originated from SGProxy', meta: { diff --git a/src/app/services/intranet/intranet.service.ts b/src/app/services/intranet/intranet.service.ts index d38e5db6f2..158b01932d 100644 --- a/src/app/services/intranet/intranet.service.ts +++ b/src/app/services/intranet/intranet.service.ts @@ -1,6 +1,9 @@ import fs from 'fs' -import { IIntranet } from '../../config/feature-manager' +import { + IIntranet, + intranetConfig, +} from '../../config/feature-manager/intranet.config' import { createLoggerWithLabel } from '../../config/logger' const logger = createLoggerWithLabel(module) @@ -8,7 +11,7 @@ const logger = createLoggerWithLabel(module) /** * Handles intranet functionality based on a given list of intranet IPs. */ -export class IntranetService { +class IntranetServiceClass { /** * List of IP addresses associated with intranet */ @@ -19,6 +22,10 @@ export class IntranetService { // e.g. intranet-only forms, then this try-catch should be removed so that // an error is thrown if the intranet IP list file does not exist. // For now, the functionality is not crucial, so we can default to an empty array. + if (!intranetConfig.intranetIpListPath) { + this.intranetIps = [] + return + } try { this.intranetIps = fs .readFileSync(intranetConfig.intranetIpListPath) @@ -44,3 +51,5 @@ export class IntranetService { return this.intranetIps.includes(ip) } } + +export const IntranetService = new IntranetServiceClass(intranetConfig) diff --git a/src/app/utils/error.ts b/src/app/utils/error.ts new file mode 100644 index 0000000000..f4b975abb2 --- /dev/null +++ b/src/app/utils/error.ts @@ -0,0 +1,25 @@ +import { MapRouteError, MapRouteErrors } from '../../types' +import { + ApplicationError, + EmptyErrorFieldError, +} from '../modules/core/core.errors' + +/** + * Used when a route-error mapper expects certain errors to be presented + * as lists. It takes the first element of an error list to pass into the + * mapper, with checks on the length for anomalies. + * @param mapRouteError The error mapper. + */ +export const genericMapRouteErrorTransform = ( + mapRouteError: MapRouteError, +): MapRouteErrors => { + return (error, coreErrorMessage) => { + let errorToMap: ApplicationError + if (Array.isArray(error)) { + errorToMap = error.length > 0 ? error[0] : new EmptyErrorFieldError() + } else { + errorToMap = error + } + return mapRouteError(errorToMap, coreErrorMessage) + } +} diff --git a/src/public/main.js b/src/public/main.js index e29766efe4..4fc9487249 100644 --- a/src/public/main.js +++ b/src/public/main.js @@ -13,7 +13,6 @@ const moduleDependencies = [ 'ngResource', 'ui.router', 'ui.bootstrap', - 'angularMoment', 'vcRecaptcha', 'users', 'ngFileUpload', @@ -55,7 +54,6 @@ require('angular-permission/dist/angular-permission') require('@opengovsg/angular-recaptcha-fallback') require('angular-resource') require('angular-sanitize') -require('angular-moment') require('angular-messages') require('angular-ui-bootstrap') @@ -85,9 +83,6 @@ const appName = 'FormSG' // Add module dependencies const app = angular.module(appName, moduleDependencies) -// Override moment using Angular's dependency injection -app.constant('moment', require('moment-timezone')) - // Setting HTML5 Location Mode angular.module(appName).config([ '$locationProvider', @@ -137,7 +132,6 @@ app.requires.push('ngIntlTelInput') // Core services require('./modules/core/services/gtag.client.service.js') -require('./modules/core/services/formsgSdk.client.factory') // Core controllers require('./modules/core/controllers/landing.client.controller.js') @@ -146,8 +140,6 @@ require('./modules/core/controllers/edit-contact-number-modal.client.controller' // Core directives require('./modules/core/directives/on-click-outside.client.directive') require('./modules/core/directives/route-loading-indicator.client.directive.js') -require('./modules/core/services/features.client.factory.js') -require('./modules/core/directives/feature-toggle.client.directive.js') // Core config require('./modules/core/config/core.client.routes.js') @@ -257,7 +249,6 @@ require('./modules/forms/config/forms.client.routes.js') // forms services require('./modules/forms/services/form-fields.client.service.js') -require('./modules/forms/services/form-factory.client.service.js') require('./modules/forms/services/form-api.client.factory.js') require('./modules/forms/services/form-error.client.factory.js') require('./modules/forms/services/spcp-session.client.factory.js') @@ -275,9 +266,6 @@ require('./modules/forms/services/mailto.client.factory.js') require('./modules/users/config/users.client.config.js') require('./modules/users/config/users.client.routes.js') -// User services -require('./modules/users/services/auth.client.service.js') - // User controllers require('./modules/users/controllers/authentication.client.controller.js') require('./modules/users/controllers/billing.client.controller.js') diff --git a/src/public/modules/core/componentViews/avatar-dropdown.html b/src/public/modules/core/componentViews/avatar-dropdown.html index 37e3c975fd..31b471699d 100644 --- a/src/public/modules/core/componentViews/avatar-dropdown.html +++ b/src/public/modules/core/componentViews/avatar-dropdown.html @@ -27,11 +27,7 @@ -